PKlv5$DDicalendar/cal.py# -*- coding: latin-1 -*- """ Calendar is a dictionary like Python object that can render itself as VCAL files according to rfc2445. These are the defined components. """ # from python from types import ListType, TupleType SequenceTypes = (ListType, TupleType) import re # from this package from icalendar.caselessdict import CaselessDict from icalendar.parser import Contentlines, Contentline, Parameters from icalendar.parser import q_split, q_join from icalendar.prop import TypesFactory, vText ###################################### # The component factory class ComponentFactory(CaselessDict): """ All components defined in rfc 2445 are registered in this factory class. To get a component you can use it like this. >>> factory = ComponentFactory() >>> component = factory['VEVENT'] >>> event = component(dtstart='19700101') >>> event.as_string() 'BEGIN:VEVENT\\r\\nDTSTART:19700101\\r\\nEND:VEVENT\\r\\n' >>> factory.get('VCALENDAR', Component) """ def __init__(self, *args, **kwargs): "Set keys to upper for initial dict" CaselessDict.__init__(self, *args, **kwargs) self['VEVENT'] = Event self['VTODO'] = Todo self['VJOURNAL'] = Journal self['VFREEBUSY'] = FreeBusy self['VTIMEZONE'] = Timezone self['VALARM'] = Alarm self['VCALENDAR'] = Calendar # These Properties have multiple property values inlined in one propertyline # seperated by comma. Use CaselessDict as simple caseless set. INLINE = CaselessDict( [(cat, 1) for cat in ('CATEGORIES', 'RESOURCES', 'FREEBUSY')] ) _marker = [] class Component(CaselessDict): """ Component is the base object for calendar, Event and the other components defined in RFC 2445. normally you will not use this class directy, but rather one of the subclasses. A component is like a dictionary with extra methods and attributes. >>> c = Component() >>> c.name = 'VCALENDAR' Every key defines a property. A property can consist of either a single item. This can be set with a single value >>> c['prodid'] = '-//max m//icalendar.mxm.dk/' >>> c VCALENDAR({'PRODID': '-//max m//icalendar.mxm.dk/'}) or with a list >>> c['ATTENDEE'] = ['Max M', 'Rasmussen'] if you use the add method you don't have to considder if a value is a list or not. >>> c = Component() >>> c.name = 'VEVENT' >>> c.add('attendee', 'maxm@mxm.dk') >>> c.add('attendee', 'test@example.dk') >>> c VEVENT({'ATTENDEE': [vCalAddress('maxm@mxm.dk'), vCalAddress('test@example.dk')]}) You can get the values back directly >>> c.add('prodid', '-//my product//') >>> c['prodid'] vText(u'-//my product//') or decoded to a python type >>> c.decoded('prodid') u'-//my product//' With default values for non existing properties >>> c.decoded('version', 'No Version') 'No Version' The component can render itself in the RFC 2445 format. >>> c = Component() >>> c.name = 'VCALENDAR' >>> c.add('attendee', 'Max M') >>> c.as_string() 'BEGIN:VCALENDAR\\r\\nATTENDEE:Max M\\r\\nEND:VCALENDAR\\r\\n' >>> from icalendar.prop import vDatetime Components can be nested, so You can add a subcompont. Eg a calendar holds events. >>> e = Component(summary='A brief history of time') >>> e.name = 'VEVENT' >>> e.add('dtend', '20000102T000000', encode=0) >>> e.add('dtstart', '20000101T000000', encode=0) >>> e.as_string() 'BEGIN:VEVENT\\r\\nDTEND:20000102T000000\\r\\nDTSTART:20000101T000000\\r\\nSUMMARY:A brief history of time\\r\\nEND:VEVENT\\r\\n' >>> c.add_component(e) >>> c.subcomponents [VEVENT({'DTEND': '20000102T000000', 'DTSTART': '20000101T000000', 'SUMMARY': 'A brief history of time'})] We can walk over nested componentes with the walk method. >>> [i.name for i in c.walk()] ['VCALENDAR', 'VEVENT'] We can also just walk over specific component types, by filtering them on their name. >>> [i.name for i in c.walk('VEVENT')] ['VEVENT'] >>> [i['dtstart'] for i in c.walk('VEVENT')] ['20000101T000000'] INLINE properties have their values on one property line. Note the double quoting of the value with a colon in it. >>> c = Calendar() >>> c['resources'] = 'Chair, Table, "Room: 42"' >>> c VCALENDAR({'RESOURCES': 'Chair, Table, "Room: 42"'}) >>> c.as_string() 'BEGIN:VCALENDAR\\r\\nRESOURCES:Chair, Table, "Room: 42"\\r\\nEND:VCALENDAR\\r\\n' The inline values must be handled by the get_inline() and set_inline() methods. >>> c.get_inline('resources', decode=0) ['Chair', 'Table', 'Room: 42'] These can also be decoded >>> c.get_inline('resources', decode=1) [u'Chair', u'Table', u'Room: 42'] You can set them directly >>> c.set_inline('resources', ['A', 'List', 'of', 'some, recources'], encode=1) >>> c['resources'] 'A,List,of,"some, recources"' and back again >>> c.get_inline('resources', decode=0) ['A', 'List', 'of', 'some, recources'] >>> c['freebusy'] = '19970308T160000Z/PT3H,19970308T200000Z/PT1H,19970308T230000Z/19970309T000000Z' >>> c.get_inline('freebusy', decode=0) ['19970308T160000Z/PT3H', '19970308T200000Z/PT1H', '19970308T230000Z/19970309T000000Z'] >>> freebusy = c.get_inline('freebusy', decode=1) >>> type(freebusy[0][0]), type(freebusy[0][1]) (, ) """ name = '' # must be defined in each component required = () # These properties are required singletons = () # These properties must only appear once multiple = () # may occur more than once exclusive = () # These properties are mutually exclusive inclusive = () # if any occurs the other(s) MUST occur ('duration', 'repeat') def __init__(self, *args, **kwargs): "Set keys to upper for initial dict" CaselessDict.__init__(self, *args, **kwargs) # set parameters here for properties that use non-default values self.subcomponents = [] # Components can be nested. # def non_complience(self, warnings=0): # """ # not implemented yet! # Returns a dict describing non compliant properties, if any. # If warnings is true it also returns warnings. # # If the parser is too strict it might prevent parsing erroneous but # otherwise compliant properties. So the parser is pretty lax, but it is # possible to test for non-complience by calling this method. # """ # nc = {} # if not getattr(self, 'name', ''): # nc['name'] = {'type':'ERROR', 'description':'Name is not defined'} # return nc ############################# # handling of property values def _encode(self, name, value, cond=1): # internal, for conditional convertion of values. if cond: klass = types_factory.for_property(name) return klass(value) return value def set(self, name, value, encode=1): if type(value) == ListType: self[name] = [self._encode(name, v, encode) for v in value] else: self[name] = self._encode(name, value, encode) def add(self, name, value, encode=1): "If property exists append, else create and set it" if name in self: oldval = self[name] value = self._encode(name, value, encode) if type(oldval) == ListType: oldval.append(value) else: self.set(name, [oldval, value], encode=0) else: self.set(name, value, encode) def _decode(self, name, value): # internal for decoding property values decoded = types_factory.from_ical(name, value) return decoded def decoded(self, name, default=_marker): "Returns decoded value of property" if name in self: value = self[name] if type(value) == ListType: return [self._decode(name, v) for v in value] return self._decode(name, value) else: if default is _marker: raise KeyError, name else: return default ######################################################################## # Inline values. A few properties have multiple values inlined in in one # property line. These methods are used for splitting and joining these. def get_inline(self, name, decode=1): """ Returns a list of values (split on comma). """ vals = [v.strip('" ').encode(vText.encoding) for v in q_split(self[name])] if decode: return [self._decode(name, val) for val in vals] return vals def set_inline(self, name, values, encode=1): """ Converts a list of values into comma seperated string and sets value to that. """ if encode: values = [self._encode(name, value, 1) for value in values] joined = q_join(values).encode(vText.encoding) self[name] = types_factory['inline'](joined) ######################### # Handling of components def add_component(self, component): "add a subcomponent to this component" self.subcomponents.append(component) def _walk(self, name): # private! result = [] if name is None or self.name == name: result.append(self) for subcomponent in self.subcomponents: result += subcomponent._walk(name) return result def walk(self, name=None): """ Recursively traverses component and subcomponents. Returns sequence of same. If name is passed, only components with name will be returned. """ if not name is None: name = name.upper() return self._walk(name) ##################### # Generation def property_items(self): """ Returns properties in this component and subcomponents as: [(name, value), ...] """ vText = types_factory['text'] properties = [('BEGIN', vText(self.name).ical())] property_names = self.keys() property_names.sort() for name in property_names: values = self[name] if type(values) == ListType: # normally one property is one line for value in values: properties.append((name, value)) else: properties.append((name, values)) # recursion is fun! for subcomponent in self.subcomponents: properties += subcomponent.property_items() properties.append(('END', vText(self.name).ical())) return properties def from_string(st, multiple=False): """ Populates the component recursively from a string """ stack = [] # a stack of components comps = [] for line in Contentlines.from_string(st): # raw parsing if not line: continue name, params, vals = line.parts() uname = name.upper() # check for start of component if uname == 'BEGIN': # try and create one of the components defined in the spec, # otherwise get a general Components for robustness. component_name = vals.upper() component_class = component_factory.get(component_name, Component) component = component_class() if not getattr(component, 'name', ''): # for undefined components component.name = component_name stack.append(component) # check for end of event elif uname == 'END': # we are done adding properties to this component # so pop it from the stack and add it to the new top. component = stack.pop() if not stack: # we are at the end comps.append(component) else: stack[-1].add_component(component) # we are adding properties to the current top of the stack else: factory = types_factory.for_property(name) vals = factory(factory.from_ical(vals)) vals.params = params stack[-1].add(name, vals, encode=0) if multiple: return comps if not len(comps) == 1: raise ValueError('Found multiple components where ' 'only one is allowed') return comps[0] from_string = staticmethod(from_string) def __repr__(self): return '%s(' % self.name + dict.__repr__(self) + ')' # def content_line(self, name): # "Returns property as content line" # value = self[name] # params = getattr(value, 'params', Parameters()) # return Contentline.from_parts((name, params, value)) def content_lines(self): "Converts the Component and subcomponents into content lines" contentlines = Contentlines() for name, values in self.property_items(): params = getattr(values, 'params', Parameters()) contentlines.append(Contentline.from_parts((name, params, values))) contentlines.append('') # remember the empty string in the end return contentlines def as_string(self): return str(self.content_lines()) def __str__(self): "Returns rendered iCalendar" return self.as_string() ####################################### # components defined in RFC 2445 class Event(Component): name = 'VEVENT' required = ('UID',) singletons = ( 'CLASS', 'CREATED', 'DESCRIPTION', 'DTSTART', 'GEO', 'LAST-MOD', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'DTSTAMP', 'SEQUENCE', 'STATUS', 'SUMMARY', 'TRANSP', 'URL', 'RECURID', 'DTEND', 'DURATION', 'DTSTART', ) exclusive = ('DTEND', 'DURATION', ) multiple = ( 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT','CONTACT', 'EXDATE', 'EXRULE', 'RSTATUS', 'RELATED', 'RESOURCES', 'RDATE', 'RRULE' ) class Todo(Component): name = 'VTODO' required = ('UID',) singletons = ( 'CLASS', 'COMPLETED', 'CREATED', 'DESCRIPTION', 'DTSTAMP', 'DTSTART', 'GEO', 'LAST-MOD', 'LOCATION', 'ORGANIZER', 'PERCENT', 'PRIORITY', 'RECURID', 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL', 'DUE', 'DURATION', ) exclusive = ('DUE', 'DURATION',) multiple = ( 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE', 'EXRULE', 'RSTATUS', 'RELATED', 'RESOURCES', 'RDATE', 'RRULE' ) class Journal(Component): name = 'VJOURNAL' required = ('UID',) singletons = ( 'CLASS', 'CREATED', 'DESCRIPTION', 'DTSTART', 'DTSTAMP', 'LAST-MOD', 'ORGANIZER', 'RECURID', 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL', ) multiple = ( 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE', 'EXRULE', 'RELATED', 'RDATE', 'RRULE', 'RSTATUS', ) class FreeBusy(Component): name = 'VFREEBUSY' required = ('UID',) singletons = ( 'CONTACT', 'DTSTART', 'DTEND', 'DURATION', 'DTSTAMP', 'ORGANIZER', 'UID', 'URL', ) multiple = ('ATTENDEE', 'COMMENT', 'FREEBUSY', 'RSTATUS',) class Timezone(Component): name = 'VTIMEZONE' required = ( 'TZID', 'STANDARDC', 'DAYLIGHTC', 'DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM' ) singletons = ('LAST-MOD', 'TZURL', 'TZID',) multiple = ('COMMENT', 'RDATE', 'RRULE', 'TZNAME',) class Alarm(Component): name = 'VALARM' # not quite sure about these ... required = ('ACTION', 'TRIGGER',) singletons = ('ATTACH', 'ACTION', 'TRIGGER', 'DURATION', 'REPEAT',) inclusive = (('DURATION', 'REPEAT',),) multiple = ('STANDARDC', 'DAYLIGHTC') class Calendar(Component): """ This is the base object for an iCalendar file. Setting up a minimal calendar component looks like this >>> cal = Calendar() Som properties are required to be compliant >>> cal['prodid'] = '-//My calendar product//mxm.dk//' >>> cal['version'] = '2.0' We also need at least one subcomponent for a calendar to be compliant >>> from datetime import datetime >>> event = Event() >>> event['summary'] = 'Python meeting about calendaring' >>> event['uid'] = '42' >>> event.set('dtstart', datetime(2005,4,4,8,0,0)) >>> cal.add_component(event) >>> cal.subcomponents[0].as_string() 'BEGIN:VEVENT\\r\\nDTSTART:20050404T080000\\r\\nSUMMARY:Python meeting about calendaring\\r\\nUID:42\\r\\nEND:VEVENT\\r\\n' Write to disc >>> import tempfile, os >>> directory = tempfile.mkdtemp() >>> open(os.path.join(directory, 'test.ics'), 'wb').write(cal.as_string()) """ name = 'VCALENDAR' required = ('prodid', 'version', ) singletons = ('prodid', 'version', ) multiple = ('calscale', 'method', ) # These are read only singleton, so one instance is enough for the module types_factory = TypesFactory() component_factory = ComponentFactory() PKC8GGGicalendar/parser.pyc; eEc@s dZdklZlZeegZdkZdklZdZei dZ ei dZ ei dZ ei dZ d Zed Zei d Zd Zd dZd dZdefdYZdefdYZdefdYZdS(s~ This module parses and generates contentlines as defined in RFC 2445 (iCalendar), but will probably work for other MIME types with similar syntax. Eg. RFC 2426 (vCard) It is stupid in the sense that it treats the content purely as strings. No type conversion is attempted. Copyright, 2005: Max M License: GPL (Just contact med if and why you would like it changed) (s TupleTypesListTypeN(s CaselessDictcCs/t|tjot|Snt|SdS(sReturns a parameter valueN(stypesvals SequenceTypessq_joinsdQuote(sval((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pysparamValss[\w-]+s [- -",:;]s [- -"]s([ ]? )+[ ]{1}cCsHti|}t|djo||djodSnt|dS(Nii(sNAMEsfindallsnamesmatchslens ValueError(snamesmatch((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pysvalidate_token&s$cCs8t}|o t}n|i|o t|ndS(N(s UNSAFE_CHARs validatorsquoteds QUNSAFE_CHARsfindallsvalues ValueError(svaluesquoteds validator((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pysvalidate_param_value,s  s[,;:].cCs$ti|o d|Sn|SdS(s Parameter values containing [,;:] must be double quoted >>> dQuote('Max') 'Max' >>> dQuote('Rasmussen, Max') '"Rasmussen, Max"' >>> dQuote('name:value') '"name:value"' s"%s"N(sQUOTABLEssearchsval(sval((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pysdQuote4s  s,cCsg}d}t|}d}xt|D]}||}|djo | }n| o ||jo"|i |||!|d}n|d|jo|i ||q+q+W|SdS(s Splits a string on char, taking double (q)uotes into considderation >>> q_split('Max,Moller,"Rasmussen, Max"') ['Max', 'Moller', '"Rasmussen, Max"'] is"iN( sresultscursorslensstslengthsinquotesrangesischssepsappend(sstssepschsinquotesiscursorslengthsresult((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pysq_splitCs      cCs8|igi}|D]}|t|q~SdS(s Joins a list on sep, quoting strings with QUOTABLE chars >>> s = ['Max', 'Moller', 'Rasmussen, Max'] >>> q_join(s) 'Max,Moller,"Rasmussen, Max"' N(ssepsjoinsappends_[1]slstsitmsdQuote(slstsseps_[1]sitm((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pysq_joinXss ParameterscBsAtZdZdZdZdZedZeeZRS(s Parser and generator of Property parameter strings. It knows nothing of datatypes. It's main concern is textual structure. Simple parameter:value pair >>> p = Parameters(parameter1='Value1') >>> str(p) 'PARAMETER1=Value1' keys are converted to upper >>> p.keys() ['PARAMETER1'] Parameters are case insensitive >>> p['parameter1'] 'Value1' >>> p['PARAMETER1'] 'Value1' Parameter with list of values must be seperated by comma >>> p = Parameters({'parameter1':['Value1', 'Value2']}) >>> str(p) 'PARAMETER1=Value1,Value2' Multiple parameters must be seperated by a semicolon >>> p = Parameters({'RSVP':'TRUE', 'ROLE':'REQ-PARTICIPANT'}) >>> str(p) 'ROLE=REQ-PARTICIPANT;RSVP=TRUE' Parameter values containing ',;:' must be double quoted >>> p = Parameters({'ALTREP':'http://www.wiz.org'}) >>> str(p) 'ALTREP="http://www.wiz.org"' list items must be quoted seperately >>> p = Parameters({'MEMBER':['MAILTO:projectA@host.com', 'MAILTO:projectB@host.com', ]}) >>> str(p) 'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"' Now the whole sheebang >>> p = Parameters({'parameter1':'Value1', 'parameter2':['Value2', 'Value3'], 'ALTREP':['http://www.wiz.org', 'value4']}) >>> str(p) 'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3' We can also parse parameter strings >>> Parameters.from_string('PARAMETER1=Value 1;param2=Value 2') Parameters({'PARAMETER1': 'Value 1', 'PARAM2': 'Value 2'}) Including empty strings >>> Parameters.from_string('param=') Parameters({'PARAM': ''}) We can also parse parameter strings >>> Parameters.from_string('MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"') Parameters({'MEMBER': ['MAILTO:projectA@host.com', 'MAILTO:projectB@host.com']}) We can also parse parameter strings >>> Parameters.from_string('ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3') Parameters({'PARAMETER1': 'Value1', 'ALTREP': ['http://www.wiz.org', 'value4'], 'PARAMETER2': ['Value2', 'Value3']}) cCs|iSdS(sw in rfc2445 keys are called parameters, so this is to be consitent with the naming conventions N(sselfskeys(sself((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pysparamsscCsdti|dSdS(Ns Parameters(s)(sdicts__repr__sself(sself((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pys__repr__scCsmg}|i}|ix=|D]5\}}t|}|id|i|fq#Wdi |SdS(Ns%s=%ss;( sresultsselfsitemsssortskeysvaluesparamValsappendsuppersjoin(sselfsitemssvaluesresultskey((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pys__str__s    !c Csxyat}xMt|dD]<}t|d\}}t|gi}t|dD]}||qX~}g} x|D]}|i do |ido0|id}t|dt| i|q~t|dt|o| i|iq~| i|q~W| o|||>> c = Contentline('Si meliora dies, ut vina, poemata reddit') >>> str(c) 'Si meliora dies, ut vina, poemata reddit' A long line gets folded >>> c = Contentline(''.join(['123456789 ']*10)) >>> str(c) '123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234\r\n 56789 123456789 123456789 ' A folded line gets unfolded >>> c = Contentline.from_string(str(c)) >>> c '123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 ' We do not fold within a UTF-8 character: >>> c = Contentline('This line has a UTF-8 character where it should be folded. Make sure it gëts folded before that character.') >>> 'ë' in str(c) True Don't fail if we fold a line that is exactly X times 74 characters long: >>> c = str(Contentline(''.join(['x']*148))) It can parse itself into parts. Which is a tuple of (name, params, vals) >>> c = Contentline('dtstart:20050101T120000') >>> c.parts() ('dtstart', Parameters({}), '20050101T120000') >>> c = Contentline('dtstart;value=datetime:20050101T120000') >>> c.parts() ('dtstart', Parameters({'VALUE': 'datetime'}), '20050101T120000') >>> c = Contentline('ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com') >>> c.parts() ('ATTENDEE', Parameters({'ROLE': 'REQ-PARTICIPANT', 'CN': 'Max Rasmussen'}), 'MAILTO:maxm@example.com') >>> str(c) 'ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com' and back again >>> parts = ('ATTENDEE', Parameters({'ROLE': 'REQ-PARTICIPANT', 'CN': 'Max Rasmussen'}), 'MAILTO:maxm@example.com') >>> Contentline.from_parts(parts) 'ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com' and again >>> parts = ('ATTENDEE', Parameters(), 'MAILTO:maxm@example.com') >>> Contentline.from_parts(parts) 'ATTENDEE:MAILTO:maxm@example.com' A value can also be any of the types defined in PropertyValues >>> from icalendar.prop import vText >>> parts = ('ATTENDEE', Parameters(), vText('MAILTO:test@example.com')) >>> Contentline.from_parts(parts) 'ATTENDEE:MAILTO:test@example.com' A value can also be unicode >>> from icalendar.prop import vText >>> parts = ('SUMMARY', Parameters(), vText(u'INternational char ')) >>> Contentline.from_parts(parts) 'SUMMARY:INternational char \xc3\xa6 \xc3\xb8 \xc3\xa5' Traversing could look like this. >>> name, params, vals = c.parts() >>> name 'ATTENDEE' >>> vals 'MAILTO:maxm@example.com' >>> for key, val in params.items(): ... (key, val) ('ROLE', 'REQ-PARTICIPANT') ('CN', 'Max Rasmussen') And the traditional failure >>> c = Contentline('ATTENDEE;maxm@example.com') >>> c.parts() Traceback (most recent call last): ... ValueError: Content line could not be parsed into parts Another failure: >>> c = Contentline(':maxm@example.com') >>> c.parts() Traceback (most recent call last): ... ValueError: Content line could not be parsed into parts >>> c = Contentline('key;param=:value') >>> c.parts() ('key', Parameters({'PARAM': ''}), 'value') >>> c = Contentline('key;param="pvalue":value') >>> c.parts() ('key', Parameters({'PARAM': 'pvalue'}), 'value') Should bomb on missing param: >>> c = Contentline.from_string("k;:no param") >>> c.parts() Traceback (most recent call last): ... ValueError: Content line could not be parsed into parts >>> c = Contentline('key;param=pvalue:value', strict=False) >>> c.parts() ('key', Parameters({'PARAM': 'pvalue'}), 'value') If strict is set to True, uppercase param values that are not double-quoted, this is because the spec says non-quoted params are case-insensitive. >>> c = Contentline('key;param=pvalue:value', strict=True) >>> c.parts() ('key', Parameters({'PARAM': 'PVALUE'}), 'value') >>> c = Contentline('key;param="pValue":value', strict=True) >>> c.parts() ('key', Parameters({'PARAM': 'pValue'}), 'value') cCs*ti||}t|d||SdS(Nsstrict(sstrs__new__sclssstsselfssetattrsstrict(sclssstsstrictsself((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pys__new__pscCsgi}|D]}|t|q~\}}}y:|otd|||fSntd||fSWn2t dt |t |t |fnXdS(s*Turns a tuple of parts into a content lines%s;%s:%ss%s:%ss&Property: %s Wrong values "%s" or "%s"N( sappends_[1]spartsspsstrsnamesparamssvaluess Contentlines ValueErrorsrepr(spartssnames_[1]spsvaluessparams((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pys from_partsus6c Cs;y$t}t}d}xtt|D]r}||}| oB|djo| o |}n|djo| o |}qn|djo | }q(q(W|| }| o t dnt ||d|jo t dnt i ||d|!d|i}||d}|||fSWnt d nXd S( sJ Splits the content line up into (name, parameters, values) parts is:;s:s"sKey name is requiredisInvalid content linesstricts+Content line could not be parsed into partsN(sNones name_splits value_splitsinquotessrangeslensselfsischsnames ValueErrorsvalidate_tokens Parameterss from_stringsstrictsparamssvalues( sselfschsnamesisinquotessparamss value_splitsvaluess name_split((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pyspartss6       cCs7y ttid|d|SWntdnXdS(sAUnfolds the content lines in an iCalendar into long content linesssstricts%Expected StringType with content lineN(s ContentlinesFOLDssubsstsstricts ValueError(sstsstrict((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pys from_strings  cCst|}g}d}d}xto||jo |}nIxEto=t||}|djp |djoPqB|d8}qBW|i |||!||joPn|}|d}q!Wdi |SdS(sFLong content lines are folded so they are less than 75 characters wideiiJiiis N( slensselfsl_lines new_linessstartsendsTruesords char_valuesappendsjoin(sselfs new_linessendsstarts char_valuesl_line((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pys__str__s*    ( s__name__s __module__s__doc__sFalses__new__s from_partss staticmethodspartss from_strings__str__(((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pys Contentlines y     s ContentlinescBs,tZdZdZdZeeZRS(s I assume that iCalendar files generally are a few kilobytes in size. Then this should be efficient. for Huge files, an iterator should probably be used instead. >>> c = Contentlines([Contentline('BEGIN:VEVENT\r\n')]) >>> str(c) 'BEGIN:VEVENT\r\n' Lets try appending it with a 100 charater wide string >>> c.append(Contentline(''.join(['123456789 ']*10)+'\r\n')) >>> str(c) 'BEGIN:VEVENT\r\n\r\n123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234\r\n 56789 123456789 123456789 \r\n' Notice that there is an extra empty string in the end of the content lines. That is so they can be easily joined with: ' '.join(contentlines)). >>> Contentlines.from_string('A short line\r\n') ['A short line', ''] >>> Contentlines.from_string('A faked\r\n long line\r\n') ['A faked long line', ''] >>> Contentlines.from_string('A faked\r\n long line\r\nAnd another lin\r\n\te that is folded\r\n') ['A faked long line', 'And another line that is folded', ''] cCsditt|SdS(sSimply join self.s N(sjoinsmapsstrsself(sself((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pys__str__scCsyktid|}gi}|iD]!}|o|t|q)q)~}|idt |SWnt dnXdS(s"Parses a string into content linesss&Expected StringType with content linesN( sFOLDssubsstsunfoldedsappends_[1]s splitlinesslines Contentlinesliness Contentliness ValueError(sstsliness_[1]sunfoldedsline((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pys from_strings> (s__name__s __module__s__doc__s__str__s from_strings staticmethod(((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pys Contentliness   (s__doc__stypess TupleTypesListTypes SequenceTypessresicalendar.caselessdicts CaselessDictsparamValscompilesNAMEs UNSAFE_CHARs QUNSAFE_CHARsFOLDsvalidate_tokensTruesvalidate_param_valuesQUOTABLEsdQuotesq_splitsq_joins Parameterssstrs Contentlineslists Contentlines(s Parameterssvalidate_param_valuesFOLDs UNSAFE_CHARsq_splitsvalidate_tokens QUNSAFE_CHARsres SequenceTypessparamValsdQuotesListTypesq_joins TupleTypesNAMEs ContentlinesQUOTABLEs Contentliness CaselessDict((s4build/bdist.darwin-8.0.1-x86/egg/icalendar/parser.pys? s$         PKg3Jrqqicalendar/__init__.py# Components from icalendar.cal import Calendar, Event, Todo, Journal from icalendar.cal import FreeBusy, Timezone, Alarm, ComponentFactory # Property Data Value Types from icalendar.prop import vBinary, vBoolean, vCalAddress, vDatetime, vDate, \ vDDDTypes, vDuration, vFloat, vInt, vPeriod, \ vWeekday, vFrequency, vRecur, vText, vTime, vUri, \ vGeo, vUTCOffset, TypesFactory # useful tzinfo subclasses from icalendar.prop import FixedOffset, UTC, LocalTimezone # Parameters and helper methods for splitting and joining string with escaped # chars. from icalendar.parser import Parameters, q_split, q_join PKg3icalendar/util.pyfrom string import ascii_letters, digits import random """ This module contains non-essential tools for iCalendar. Pretty thin so far eh? """ class UIDGenerator: """ If you are too lazy to create real uids. NOTE: this doctest is disabled (only two > instead of three) Automatic semi-random uid >> g = UIDGenerator() >> uid = g.uid() >> uid.ical() '20050109T153222-7ekDDHKcw46QlwZK@example.com' You should at least insert your own hostname to be more compliant >> g = UIDGenerator() >> uid = g.uid('Example.ORG') >> uid.ical() '20050109T153549-NbUItOPDjQj8Ux6q@Example.ORG' You can also insert a path or similar >> g = UIDGenerator() >> uid = g.uid('Example.ORG', '/path/to/content') >> uid.ical() '20050109T153415-/path/to/content@Example.ORG' """ chars = list(ascii_letters + digits) def rnd_string(self, length=16): "Generates a string with random characters of length" return ''.join([random.choice(self.chars) for i in range(length)]) def uid(self, host_name='example.com', unique=''): """ Generates a unique id consisting of: datetime-uniquevalue@host. Like: 20050105T225746Z-HKtJMqUgdO0jDUwm@example.com """ from PropertyValues import vText, vDatetime unique = unique or self.rnd_string() return vText('%s-%s@%s' % (vDatetime.today().ical(), unique, host_name)) PKC8ܑ22icalendar/caselessdict.pyc; σdEc@sdefdYZdS(s CaselessDictcBstZdZdZdZdZdZdZedZ edZ edZ d Z d Z d Zd Zd ZdZRS(s~ A dictionary that isn't case sensitive, and only use string as keys. >>> ncd = CaselessDict(key1='val1', key2='val2') >>> ncd CaselessDict({'KEY2': 'val2', 'KEY1': 'val1'}) >>> ncd['key1'] 'val1' >>> ncd['KEY1'] 'val1' >>> ncd['KEY3'] = 'val3' >>> ncd['key3'] 'val3' >>> ncd.setdefault('key3', 'FOUND') 'val3' >>> ncd.setdefault('key4', 'NOT FOUND') 'NOT FOUND' >>> ncd['key4'] 'NOT FOUND' >>> ncd.get('key1') 'val1' >>> ncd.get('key3', 'NOT FOUND') 'val3' >>> ncd.get('key4', 'NOT FOUND') 'NOT FOUND' >>> 'key4' in ncd True >>> del ncd['key4'] >>> ncd.has_key('key4') False >>> ncd.update({'key5':'val5', 'KEY6':'val6', 'KEY5':'val7'}) >>> ncd['key6'] 'val6' >>> keys = ncd.keys() >>> keys.sort() >>> keys ['KEY1', 'KEY2', 'KEY3', 'KEY5', 'KEY6'] cOskti|||xQ|iD]C\}}|i}||joti |||||> g = UIDGenerator() >> uid = g.uid() >> uid.ical() '20050109T153222-7ekDDHKcw46QlwZK@example.com' You Should at least insert your own hostname to be more complient >> g = UIDGenerator() >> uid = g.uid('Example.ORG') >> uid.ical() '20050109T153549-NbUItOPDjQj8Ux6q@Example.ORG' You can also insert a path or similar >> g = UIDGenerator() >> uid = g.uid('Example.ORG', '/path/to/content') >> uid.ical() '20050109T153415-/path/to/content@Example.ORG' icCsDdigi}t|D]}|ti|i q~SdS(s3Generates a string with random characters of lengthsN( sjoinsappends_[1]srangeslengthsisrandomschoicesselfschars(sselfslengthsis_[1]((s3build/bdist.darwin-8.0.1-x86/egg/icalendar/tools.pys rnd_string#ss example.comscCsMdkl}l}|p |i}|d|ii||fSdS(s Generates a unique id consisting of: datetime-uniquevalue@host. Like: 20050105T225746Z-HKtJMqUgdO0jDUwm@example.com (svTexts vDatetimes%s-%s@%sN( sPropertyValuessvTexts vDatetimesuniquesselfs rnd_stringstodaysicals host_name(sselfs host_namesuniques vDatetimesvText((s3build/bdist.darwin-8.0.1-x86/egg/icalendar/tools.pysuid's( s__name__s __module__s__doc__slists ascii_letterssdigitsscharss rnd_stringsuid(((s3build/bdist.darwin-8.0.1-x86/egg/icalendar/tools.pys UIDGenerator s  s__main__( sstrings ascii_letterssdigitssrandoms UIDGenerators__name__sos.pathsossdocteststoolsstestmod(sdigitsstoolss ascii_letterssrandomsdoctests UIDGeneratorsos((s3build/bdist.darwin-8.0.1-x86/egg/icalendar/tools.pys?s  ) PKev5\icalendar/prop.py# -*- coding: latin-1 -*- """ This module contains the parser/generators (or coders/encoders if you prefer) for the classes/datatypes that are used in Icalendar: ########################################################################### # This module defines these property value data types and property parameters 4.2 Defined property parameters are: ALTREP, CN, CUTYPE, DELEGATED-FROM, DELEGATED-TO, DIR, ENCODING, FMTTYPE, FBTYPE, LANGUAGE, MEMBER, PARTSTAT, RANGE, RELATED, RELTYPE, ROLE, RSVP, SENT-BY, TZID, VALUE 4.3 Defined value data types are: BINARY, BOOLEAN, CAL-ADDRESS, DATE, DATE-TIME, DURATION, FLOAT, INTEGER, PERIOD, RECUR, TEXT, TIME, URI, UTC-OFFSET ########################################################################### iCalendar properties has values. The values are strongly typed. This module defines these types, calling val.ical() on them, Will render them as defined in rfc2445. If you pass any of these classes a Python primitive, you will have an object that can render itself as iCalendar formatted date. Property Value Data Types starts with a 'v'. they all have an ical() and from_ical() method. The ical() method generates a text string in the iCalendar format. The from_ical() method can parse this format and return a primitive Python datatype. So it should allways be true that: x == vDataType.from_ical(VDataType(x).ical()) These types are mainly used for parsing and file generation. But you can set them directly. """ # from python >= 2.3 from datetime import datetime, timedelta, time, date, tzinfo from types import IntType, StringType, UnicodeType, TupleType, ListType SequenceTypes = [TupleType, ListType] import re import time as _time # from this package from icalendar.caselessdict import CaselessDict from icalendar.parser import Parameters DATE_PART = r'(\d+)D' TIME_PART = r'T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?' DATETIME_PART = '(?:%s)?(?:%s)?' % (DATE_PART, TIME_PART) WEEKS_PART = r'(\d+)W' DURATION_REGEX = re.compile(r'([-+]?)P(?:%s|%s)$' % (WEEKS_PART, DATETIME_PART)) WEEKDAY_RULE = re.compile('(?P[+-]?)(?P[\d]?)' '(?P[\w]{2})$') class vBinary: """ Binary property values are base 64 encoded >>> b = vBinary('This is gibberish') >>> b.ical() 'VGhpcyBpcyBnaWJiZXJpc2g=' >>> b = vBinary.from_ical('VGhpcyBpcyBnaWJiZXJpc2g=') >>> b 'This is gibberish' The roundtrip test >>> x = 'Binary data \x13 \x56' >>> vBinary(x).ical() 'QmluYXJ5IGRhdGEg5iD4IOUgEyBW' >>> vBinary.from_ical('QmluYXJ5IGRhdGEg5iD4IOUgEyBW') 'Binary data \\xe6 \\xf8 \\xe5 \\x13 V' >>> b = vBinary('txt') >>> b.params Parameters({'VALUE': 'BINARY', 'ENCODING': 'BASE64'}) """ def __init__(self, obj): self.obj = obj self.params = Parameters(encoding='BASE64', value="BINARY") def __repr__(self): return "vBinary(%s)" % str.__repr__(self.obj) def ical(self): return self.obj.encode('base-64')[:-1] def from_ical(ical): "Parses the data format from ical text format" try: return ical.decode('base-64') except: raise ValueError, 'Not valid base 64 encoding.' from_ical = staticmethod(from_ical) def __str__(self): return self.ical() class vBoolean(int): """ Returns specific string according to state >>> bin = vBoolean(True) >>> bin.ical() 'TRUE' >>> bin = vBoolean(0) >>> bin.ical() 'FALSE' The roundtrip test >>> x = True >>> x == vBoolean.from_ical(vBoolean(x).ical()) True >>> vBoolean.from_ical('true') True """ def __init__(self, *args, **kwargs): int.__init__(self, *args, **kwargs) self.params = Parameters() def ical(self): if self: return 'TRUE' return 'FALSE' bool_map = CaselessDict(true=True, false=False) def from_ical(ical): "Parses the data format from ical text format" try: return vBoolean.bool_map[ical] except: raise ValueError, "Expected 'TRUE' or 'FALSE'. Got %s" % ical from_ical = staticmethod(from_ical) def __str__(self): return self.ical() class vCalAddress(str): """ This just returns an unquoted string >>> a = vCalAddress('MAILTO:maxm@mxm.dk') >>> a.params['cn'] = 'Max M' >>> a.ical() 'MAILTO:maxm@mxm.dk' >>> str(a) 'MAILTO:maxm@mxm.dk' >>> a.params Parameters({'CN': 'Max M'}) >>> vCalAddress.from_ical('MAILTO:maxm@mxm.dk') 'MAILTO:maxm@mxm.dk' """ def __init__(self, *args, **kwargs): str.__init__(self, *args, **kwargs) self.params = Parameters() def __repr__(self): return u"vCalAddress(%s)" % str.__repr__(self) def ical(self): return str(self) def from_ical(ical): "Parses the data format from ical text format" try: return str(ical) except: raise ValueError, 'Expected vCalAddress, got: %s' % ical from_ical = staticmethod(from_ical) def __str__(self): return str.__str__(self) #################################################### # handy tzinfo classes you can use. ZERO = timedelta(0) HOUR = timedelta(hours=1) STDOFFSET = timedelta(seconds = -_time.timezone) if _time.daylight: DSTOFFSET = timedelta(seconds = -_time.altzone) else: DSTOFFSET = STDOFFSET DSTDIFF = DSTOFFSET - STDOFFSET class FixedOffset(tzinfo): """Fixed offset in minutes east from UTC.""" def __init__(self, offset, name): self.__offset = timedelta(minutes = offset) self.__name = name def utcoffset(self, dt): return self.__offset def tzname(self, dt): return self.__name def dst(self, dt): return ZERO class UTC(tzinfo): """UTC tzinfo subclass""" def utcoffset(self, dt): return ZERO def tzname(self, dt): return "UTC" def dst(self, dt): return ZERO UTC = UTC() class LocalTimezone(tzinfo): """ Timezone of the machine where the code is running """ def utcoffset(self, dt): if self._isdst(dt): return DSTOFFSET else: return STDOFFSET def dst(self, dt): if self._isdst(dt): return DSTDIFF else: return ZERO def tzname(self, dt): return _time.tzname[self._isdst(dt)] def _isdst(self, dt): tt = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.weekday(), 0, -1) stamp = _time.mktime(tt) tt = _time.localtime(stamp) return tt.tm_isdst > 0 #################################################### class vDatetime: """ Render and generates iCalendar datetime format. Important: if tzinfo is defined it renders itself as "date with utc time" Meaning that it has a 'Z' appended, and is in absolute time. >>> d = datetime(2001, 1,1, 12, 30, 0) >>> dt = vDatetime(d) >>> dt.ical() '20010101T123000' >>> vDatetime.from_ical('20000101T120000') datetime.datetime(2000, 1, 1, 12, 0) >>> dutc = datetime(2001, 1,1, 12, 30, 0, tzinfo=UTC) >>> vDatetime(dutc).ical() '20010101T123000Z' >>> vDatetime.from_ical('20010101T000000') datetime.datetime(2001, 1, 1, 0, 0) >>> vDatetime.from_ical('20010101T000000A') Traceback (most recent call last): ... ValueError: Wrong datetime format: 20010101T000000A >>> utc = vDatetime.from_ical('20010101T000000Z') >>> vDatetime(utc).ical() '20010101T000000Z' """ def __init__(self, dt): self.dt = dt self.params = Parameters() def ical(self): if self.dt.tzinfo: offset = self.dt.tzinfo.utcoffset(datetime.now()) utc_time = self.dt - self.dt.tzinfo.utcoffset(datetime.now()) return utc_time.strftime("%Y%m%dT%H%M%SZ") return self.dt.strftime("%Y%m%dT%H%M%S") def from_ical(ical): "Parses the data format from ical text format" try: timetuple = map(int, (( ical[:4], # year ical[4:6], # month ical[6:8], # day ical[9:11], # hour ical[11:13], # minute ical[13:15], # second ))) if not ical[15:]: return datetime(*timetuple) elif ical[15:16] == 'Z': timetuple += [0, UTC] return datetime(*timetuple) else: raise ValueError, ical except: raise ValueError, 'Wrong datetime format: %s' % ical from_ical = staticmethod(from_ical) def __str__(self): return self.ical() class vDate: """ Render and generates iCalendar date format. >>> d = date(2001, 1,1) >>> vDate(d).ical() '20010101' >>> vDate.from_ical('20010102') datetime.date(2001, 1, 2) >>> vDate('d').ical() Traceback (most recent call last): ... ValueError: Value MUST be a date instance """ def __init__(self, dt): if not isinstance(dt, date): raise ValueError('Value MUST be a date instance') self.dt = dt self.params = Parameters() def ical(self): return self.dt.strftime("%Y%m%d") def from_ical(ical): "Parses the data format from ical text format" try: timetuple = map(int, (( ical[:4], # year ical[4:6], # month ical[6:8], # day ))) return date(*timetuple) except: raise ValueError, 'Wrong date format %s' % ical from_ical = staticmethod(from_ical) def __str__(self): return self.ical() class vDuration: """ Subclass of timedelta that renders itself in the iCalendar DURATION format. >>> vDuration(timedelta(11)).ical() 'P11D' >>> vDuration(timedelta(-14)).ical() '-P14D' >>> vDuration(timedelta(1, 7384)).ical() 'P1DT2H3M4S' >>> vDuration(timedelta(1, 7380)).ical() 'P1DT2H3M' >>> vDuration(timedelta(1, 7200)).ical() 'P1DT2H' >>> vDuration(timedelta(0, 7200)).ical() 'PT2H' >>> vDuration(timedelta(0, 7384)).ical() 'PT2H3M4S' >>> vDuration(timedelta(0, 184)).ical() 'PT3M4S' >>> vDuration(timedelta(0, 22)).ical() 'PT22S' >>> vDuration(timedelta(0, 3622)).ical() 'PT1H0M22S' How does the parsing work? >>> vDuration.from_ical('PT1H0M22S') datetime.timedelta(0, 3622) >>> vDuration.from_ical('kox') Traceback (most recent call last): ... ValueError: Invalid iCalendar duration: kox >>> vDuration.from_ical('-P14D') datetime.timedelta(-14) >>> vDuration(11) Traceback (most recent call last): ... ValueError: Value MUST be a timedelta instance """ def __init__(self, td): if not isinstance(td, timedelta): raise ValueError('Value MUST be a timedelta instance') self.td = td self.params = Parameters() def ical(self): sign = "" if self.td.days < 0: sign = "-" timepart = "" if self.td.seconds: timepart = "T" hours = self.td.seconds // 3600 minutes = self.td.seconds % 3600 // 60 seconds = self.td.seconds % 60 if hours: timepart += "%dH" % hours if minutes or (hours and seconds): timepart += "%dM" % minutes if seconds: timepart += "%dS" % seconds if self.td.days == 0 and timepart: return "%sP%s" % (sign, timepart) else: return "%sP%dD%s" % (sign, abs(self.td.days), timepart) def from_ical(ical): """ Parses the data format from ical text format. """ try: match = DURATION_REGEX.match(ical) sign, weeks, days, hours, minutes, seconds = match.groups() if weeks: value = timedelta(weeks=int(weeks)) else: value = timedelta(days=int(days or 0), hours=int(hours or 0), minutes=int(minutes or 0), seconds=int(seconds or 0)) if sign == '-': value = -value return value except: raise ValueError('Invalid iCalendar duration: %s' % ical) from_ical = staticmethod(from_ical) def __str__(self): return self.ical() class vFloat(float): """ Just a float. >>> f = vFloat(1.0) >>> f.ical() '1.0' >>> vFloat.from_ical('42') 42.0 >>> vFloat(42).ical() '42.0' """ def __init__(self, *args, **kwargs): float.__init__(self, *args, **kwargs) self.params = Parameters() def ical(self): return str(self) def from_ical(ical): "Parses the data format from ical text format" try: return float(ical) except: raise ValueError, 'Expected float value, got: %s' % ical from_ical = staticmethod(from_ical) class vInt(int): """ Just an int. >>> f = vInt(42) >>> f.ical() '42' >>> vInt.from_ical('13') 13 >>> vInt.from_ical('1s3') Traceback (most recent call last): ... ValueError: Expected int, got: 1s3 """ def __init__(self, *args, **kwargs): int.__init__(self, *args, **kwargs) self.params = Parameters() def ical(self): return str(self) def from_ical(ical): "Parses the data format from ical text format" try: return int(ical) except: raise ValueError, 'Expected int, got: %s' % ical from_ical = staticmethod(from_ical) class vDDDTypes: """ A combined Datetime, Date or Duration parser/generator. Their format cannot be confused, and often values can be of either types. So this is practical. >>> d = vDDDTypes.from_ical('20010101T123000') >>> type(d) >>> repr(vDDDTypes.from_ical('20010101T123000Z'))[:65] 'datetime.datetime(2001, 1, 1, 12, 30, tzinfo=>> d = vDDDTypes.from_ical('20010101') >>> type(d) >>> vDDDTypes.from_ical('P31D') datetime.timedelta(31) >>> vDDDTypes.from_ical('-P31D') datetime.timedelta(-31) Bad input >>> vDDDTypes(42) Traceback (most recent call last): ... ValueError: You must use datetime, date or timedelta """ def __init__(self, dt): "Returns vDate from" wrong_type_used = 1 for typ in (datetime, date, timedelta): if isinstance(dt, typ): wrong_type_used = 0 if wrong_type_used: raise ValueError ('You must use datetime, date or timedelta') self.dt = dt def ical(self): dt = self.dt if isinstance(dt, datetime): return vDatetime(dt).ical() elif isinstance(dt, date): return vDate(dt).ical() elif isinstance(dt, timedelta): return vDuration(dt).ical() else: raise ValueEror ('Unknown date type') def from_ical(ical): "Parses the data format from ical text format" u = ical.upper() if u.startswith('-P') or u.startswith('P'): return vDuration.from_ical(ical) try: return vDatetime.from_ical(ical) except: return vDate.from_ical(ical) from_ical = staticmethod(from_ical) def __str__(self): return self.ical() class vPeriod: """ A precise period of time. One day in exact datetimes >>> per = (datetime(2000,1,1), datetime(2000,1,2)) >>> p = vPeriod(per) >>> p.ical() '20000101T000000/20000102T000000' >>> per = (datetime(2000,1,1), timedelta(days=31)) >>> p = vPeriod(per) >>> p.ical() '20000101T000000/P31D' Roundtrip >>> p = vPeriod.from_ical('20000101T000000/20000102T000000') >>> p (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 1, 2, 0, 0)) >>> vPeriod(p).ical() '20000101T000000/20000102T000000' >>> vPeriod.from_ical('20000101T000000/P31D') (datetime.datetime(2000, 1, 1, 0, 0), datetime.timedelta(31)) Roundtrip with absolute time >>> p = vPeriod.from_ical('20000101T000000Z/20000102T000000Z') >>> vPeriod(p).ical() '20000101T000000Z/20000102T000000Z' And an error >>> vPeriod.from_ical('20000101T000000/Psd31D') Traceback (most recent call last): ... ValueError: Expected period format, got: 20000101T000000/Psd31D Utc datetime >>> da_tz = FixedOffset(+1.0, 'da_DK') >>> start = datetime(2000,1,1, tzinfo=da_tz) >>> end = datetime(2000,1,2, tzinfo=da_tz) >>> per = (start, end) >>> vPeriod(per).ical() '19991231T235900Z/20000101T235900Z' >>> p = vPeriod((datetime(2000,1,1, tzinfo=da_tz), timedelta(days=31))) >>> p.ical() '19991231T235900Z/P31D' """ def __init__(self, per): start, end_or_duration = per if not (isinstance(start, datetime) or isinstance(start, date)): raise ValueError('Start value MUST be a datetime or date instance') if not (isinstance(end_or_duration, datetime) or isinstance(end_or_duration, date) or isinstance(end_or_duration, timedelta)): raise ValueError('end_or_duration MUST be a datetime, date or timedelta instance') self.start = start self.end_or_duration = end_or_duration self.by_duration = 0 if isinstance(end_or_duration, timedelta): self.by_duration = 1 self.duration = end_or_duration self.end = self.start + self.duration else: self.end = end_or_duration self.duration = self.end - self.start if self.start > self.end: raise ValueError("Start time is greater than end time") self.params = Parameters() def __cmp__(self, other): if not isinstance(other, vPeriod): raise NotImplementedError( 'Cannot compare vPeriod with %s' % repr(other)) return cmp((self.start, self.end), (other.start, other.end)) def overlaps(self, other): if self.start > other.start: return other.overlaps(self) if self.start <= other.start < self.end: return True return False def ical(self): if self.by_duration: return '%s/%s' % (vDatetime(self.start).ical(), vDuration(self.duration).ical()) return '%s/%s' % (vDatetime(self.start).ical(), vDatetime(self.end).ical()) def from_ical(ical): "Parses the data format from ical text format" try: start, end_or_duration = ical.split('/') start = vDDDTypes.from_ical(start) end_or_duration = vDDDTypes.from_ical(end_or_duration) return (start, end_or_duration) except: raise ValueError, 'Expected period format, got: %s' % ical from_ical = staticmethod(from_ical) def __str__(self): return self.ical() def __repr__(self): if self.by_duration: p = (self.start, self.duration) else: p = (self.start, self.end) return 'vPeriod(%s)' % repr(p) class vWeekday(str): """ This returns an unquoted weekday abbrevation >>> a = vWeekday('mo') >>> a.ical() 'MO' >>> a = vWeekday('erwer') Traceback (most recent call last): ... ValueError: Expected weekday abbrevation, got: ERWER >>> vWeekday.from_ical('mo') 'MO' >>> vWeekday.from_ical('+3mo') '+3MO' >>> vWeekday.from_ical('Saturday') Traceback (most recent call last): ... ValueError: Expected weekday abbrevation, got: Saturday >>> a = vWeekday('+mo') >>> a.ical() '+MO' >>> a = vWeekday('+3mo') >>> a.ical() '+3MO' >>> a = vWeekday('-tu') >>> a.ical() '-TU' """ week_days = CaselessDict({"SU":0, "MO":1, "TU":2, "WE":3, "TH":4, "FR":5, "SA":6}) def __init__(self, *args, **kwargs): str.__init__(self, *args, **kwargs) match = WEEKDAY_RULE.match(self) if match is None: raise ValueError, 'Expected weekday abbrevation, got: %s' % self match = match.groupdict() sign = match['signal'] weekday = match['weekday'] relative = match['relative'] if not weekday in vWeekday.week_days or sign not in '+-': raise ValueError, 'Expected weekday abbrevation, got: %s' % self self.relative = relative and int(relative) or None self.params = Parameters() def ical(self): return self.upper() def from_ical(ical): "Parses the data format from ical text format" try: return vWeekday(ical.upper()) except: raise ValueError, 'Expected weekday abbrevation, got: %s' % ical from_ical = staticmethod(from_ical) def __str__(self): return self.ical() class vFrequency(str): """ A simple class that catches illegal values. >>> f = vFrequency('bad test') Traceback (most recent call last): ... ValueError: Expected frequency, got: BAD TEST >>> vFrequency('daily').ical() 'DAILY' >>> vFrequency('daily').from_ical('MONTHLY') 'MONTHLY' """ frequencies = CaselessDict({ "SECONDLY":"SECONDLY", "MINUTELY":"MINUTELY", "HOURLY":"HOURLY", "DAILY":"DAILY", "WEEKLY":"WEEKLY", "MONTHLY":"MONTHLY", "YEARLY":"YEARLY", }) def __init__(self, *args, **kwargs): str.__init__(self, *args, **kwargs) if not self in vFrequency.frequencies: raise ValueError, 'Expected frequency, got: %s' % self self.params = Parameters() def ical(self): return self.upper() def from_ical(ical): "Parses the data format from ical text format" try: return vFrequency(ical.upper()) except: raise ValueError, 'Expected weekday abbrevation, got: %s' % ical from_ical = staticmethod(from_ical) def __str__(self): return self.ical() class vRecur(CaselessDict): """ Let's see how close we can get to one from the rfc: FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30 >>> r = dict(freq='yearly', interval=2) >>> r['bymonth'] = 1 >>> r['byday'] = 'su' >>> r['byhour'] = [8,9] >>> r['byminute'] = 30 >>> r = vRecur(r) >>> r.ical() 'BYHOUR=8,9;BYDAY=SU;BYMINUTE=30;BYMONTH=1;FREQ=YEARLY;INTERVAL=2' >>> r = vRecur(FREQ='yearly', INTERVAL=2) >>> r['BYMONTH'] = 1 >>> r['BYDAY'] = 'su' >>> r['BYHOUR'] = [8,9] >>> r['BYMINUTE'] = 30 >>> r.ical() 'BYDAY=SU;BYMINUTE=30;BYMONTH=1;INTERVAL=2;FREQ=YEARLY;BYHOUR=8,9' >>> r = vRecur(freq='DAILY', count=10) >>> r['bysecond'] = [0, 15, 30, 45] >>> r.ical() 'COUNT=10;FREQ=DAILY;BYSECOND=0,15,30,45' >>> r = vRecur(freq='DAILY', until=datetime(2005,1,1,12,0,0)) >>> r.ical() 'FREQ=DAILY;UNTIL=20050101T120000' How do we fare with regards to parsing? >>> r = vRecur.from_ical('FREQ=DAILY;INTERVAL=2;COUNT=10') >>> r {'COUNT': [10], 'FREQ': ['DAILY'], 'INTERVAL': [2]} >>> vRecur(r).ical() 'COUNT=10;FREQ=DAILY;INTERVAL=2' >>> r = vRecur.from_ical('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=-SU;BYHOUR=8,9;BYMINUTE=30') >>> r {'BYHOUR': [8, 9], 'BYDAY': ['-SU'], 'BYMINUTE': [30], 'BYMONTH': [1], 'FREQ': ['YEARLY'], 'INTERVAL': [2]} >>> vRecur(r).ical() 'BYDAY=-SU;BYMINUTE=30;INTERVAL=2;BYMONTH=1;FREQ=YEARLY;BYHOUR=8,9' Some examples from the spec >>> r = vRecur.from_ical('FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1') >>> vRecur(r).ical() 'BYSETPOS=-1;FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR' >>> r = vRecur.from_ical('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30') >>> vRecur(r).ical() 'BYDAY=SU;BYMINUTE=30;INTERVAL=2;BYMONTH=1;FREQ=YEARLY;BYHOUR=8,9' and some errors >>> r = vRecur.from_ical('BYDAY=12') Traceback (most recent call last): ... ValueError: Error in recurrence rule: BYDAY=12 """ frequencies = ["SECONDLY", "MINUTELY", "HOURLY", "DAILY", "WEEKLY", "MONTHLY", "YEARLY"] types = CaselessDict({ 'COUNT':vInt, 'INTERVAL':vInt, 'BYSECOND':vInt, 'BYMINUTE':vInt, 'BYHOUR':vInt, 'BYMONTHDAY':vInt, 'BYYEARDAY':vInt, 'BYMONTH':vInt, 'UNTIL':vDDDTypes, 'BYSETPOS':vInt, 'WKST':vWeekday, 'BYDAY':vWeekday, 'FREQ':vFrequency }) def __init__(self, *args, **kwargs): CaselessDict.__init__(self, *args, **kwargs) self.params = Parameters() def ical(self): # SequenceTypes result = [] for key, vals in self.items(): typ = self.types[key] if not type(vals) in SequenceTypes: vals = [vals] vals = ','.join([typ(val).ical() for val in vals]) result.append('%s=%s' % (key, vals)) return ';'.join(result) def parse_type(key, values): # integers parser = vRecur.types.get(key, vText) return [parser.from_ical(v) for v in values.split(',')] parse_type = staticmethod(parse_type) def from_ical(ical): "Parses the data format from ical text format" try: recur = vRecur() for pairs in ical.split(';'): key, vals = pairs.split('=') recur[key] = vRecur.parse_type(key, vals) return dict(recur) except: raise ValueError, 'Error in recurrence rule: %s' % ical from_ical = staticmethod(from_ical) def __str__(self): return self.ical() class vText(unicode): """ Simple text >>> t = vText(u'Simple text') >>> t.ical() 'Simple text' Escaped text >>> t = vText('Text ; with escaped, chars') >>> t.ical() 'Text \\\\; with escaped\\\\, chars' Escaped newlines >>> vText('Text with escaped\N chars').ical() 'Text with escaped\\\\n chars' If you pass a unicode object, it will be utf-8 encoded. As this is the (only) standard that RFC 2445 support. >>> t = vText(u'international chars ') >>> t.ical() 'international chars \\xc3\\xa6\\xc3\\xb8\\xc3\\xa5 \\xc3\\x86\\xc3\\x98\\xc3\\x85 \\xc3\\xbc' Unicode is converted to utf-8 >>> t = vText(u'international ') >>> str(t) 'international \\xc3\\xa6 \\xc3\\xb8 \\xc3\\xa5' and parsing? >>> vText.from_ical('Text \\; with escaped\\, chars') u'Text ; with escaped, chars' >>> print vText.from_ical('A string with\\; some\\\\ characters in\\Nit') A string with; some\\ characters in it """ encoding = 'utf-8' def __init__(self, *args, **kwargs): unicode.__init__(self, *args, **kwargs) self.params = Parameters() def escape(self): """ Format value according to iCalendar TEXT escaping rules. """ return (self.replace('\N', '\n') .replace('\\', '\\\\') .replace(';', r'\;') .replace(',', r'\,') .replace('\r\n', r'\n') .replace('\n', r'\n') ) def __repr__(self): return u"vText(%s)" % unicode.__repr__(self) def ical(self): return self.escape().encode(self.encoding) def from_ical(ical): "Parses the data format from ical text format" try: ical = (ical.replace(r'\N', r'\n') .replace(r'\r\n', '\n') .replace(r'\n', '\n') .replace(r'\,', ',') .replace(r'\;', ';') .replace('\\\\', '\\')) return ical.decode(vText.encoding) except: raise ValueError, 'Expected ical text, got: %s' % ical from_ical = staticmethod(from_ical) def __str__(self): return self.ical() class vTime(time): """ A subclass of datetime, that renders itself in the iCalendar time format. >>> dt = vTime(12, 30, 0) >>> dt.ical() '123000' >>> vTime.from_ical('123000') datetime.time(12, 30) We should also fail, right? >>> vTime.from_ical('263000') Traceback (most recent call last): ... ValueError: Expected time, got: 263000 """ def __init__(self, *args, **kwargs): time.__init__(self, *args, **kwargs) self.params = Parameters() def ical(self): return self.strftime("%H%M%S") def from_ical(ical): "Parses the data format from ical text format" try: timetuple = map(int, (ical[:2],ical[2:4],ical[4:6])) return time(*timetuple) except: raise ValueError, 'Expected time, got: %s' % ical from_ical = staticmethod(from_ical) def __str__(self): return self.ical() class vUri(str): """ Uniform resource identifier is basically just an unquoted string. >>> u = vUri('http://www.example.com/') >>> u.ical() 'http://www.example.com/' >>> vUri.from_ical('http://www.example.com/') # doh! 'http://www.example.com/' """ def __init__(self, *args, **kwargs): str.__init__(self, *args, **kwargs) self.params = Parameters() def ical(self): return str(self) def from_ical(ical): "Parses the data format from ical text format" try: return str(ical) except: raise ValueError, 'Expected , got: %s' % ical from_ical = staticmethod(from_ical) def __str__(self): return str.__str__(self) class vGeo: """ A special type that is only indirectly defined in the rfc. >>> g = vGeo((1.2, 3.0)) >>> g.ical() '1.2;3.0' >>> g = vGeo.from_ical('37.386013;-122.082932') >>> g (37.386012999999998, -122.082932) >>> vGeo(g).ical() '37.386013;-122.082932' >>> vGeo('g').ical() Traceback (most recent call last): ... ValueError: Input must be (float, float) for latitude and longitude """ def __init__(self, geo): try: latitude, longitude = geo latitude = float(latitude) longitude = float(longitude) except: raise ValueError('Input must be (float, float) for latitude and longitude') self.latitude = latitude self.longitude = longitude self.params = Parameters() def ical(self): return '%s;%s' % (self.latitude, self.longitude) def from_ical(ical): "Parses the data format from ical text format" try: latitude, longitude = ical.split(';') return (float(latitude), float(longitude)) except: raise ValueError, "Expected 'float;float' , got: %s" % ical from_ical = staticmethod(from_ical) def __str__(self): return self.ical() class vUTCOffset: """ Renders itself as a utc offset >>> u = vUTCOffset(timedelta(hours=2)) >>> u.ical() '+0200' >>> u = vUTCOffset(timedelta(hours=-5)) >>> u.ical() '-0500' >>> u = vUTCOffset(timedelta()) >>> u.ical() '0000' >>> u = vUTCOffset(timedelta(minutes=-30)) >>> u.ical() '-0030' >>> u = vUTCOffset(timedelta(hours=2, minutes=-30)) >>> u.ical() '+0130' >>> u = vUTCOffset(timedelta(hours=1, minutes=30)) >>> u.ical() '+0130' Parsing >>> vUTCOffset.from_ical('0000') datetime.timedelta(0) >>> vUTCOffset.from_ical('-0030') datetime.timedelta(-1, 84600) >>> vUTCOffset.from_ical('+0200') datetime.timedelta(0, 7200) >>> o = vUTCOffset.from_ical('+0230') >>> vUTCOffset(o).ical() '+0230' And a few failures >>> vUTCOffset.from_ical('+323k') Traceback (most recent call last): ... ValueError: Expected utc offset, got: +323k >>> vUTCOffset.from_ical('+2400') Traceback (most recent call last): ... ValueError: Offset must be less than 24 hours, was +2400 """ def __init__(self, td): if not isinstance(td, timedelta): raise ValueError('Offset value MUST be a timedelta instance') self.td = td self.params = Parameters() def ical(self): td = self.td day_in_minutes = (td.days * 24 * 60) seconds_in_minutes = td.seconds // 60 total_minutes = day_in_minutes + seconds_in_minutes if total_minutes == 0: sign = '%s' elif total_minutes < 0: sign = '-%s' else: sign = '+%s' hours = abs(total_minutes) // 60 minutes = total_minutes % 60 duration = '%02i%02i' % (hours, minutes) return sign % duration def from_ical(ical): "Parses the data format from ical text format" try: sign, hours, minutes = (ical[-5:-4], int(ical[-4:-2]), int(ical[-2:])) offset = timedelta(hours=hours, minutes=minutes) except: raise ValueError, 'Expected utc offset, got: %s' % ical if offset >= timedelta(hours=24): raise ValueError, 'Offset must be less than 24 hours, was %s' % ical if sign == '-': return -offset return offset from_ical = staticmethod(from_ical) def __str__(self): return self.ical() class vInline(str): """ This is an especially dumb class that just holds raw unparsed text and has parameters. Conversion of inline values are handled by the Component class, so no further processing is needed. >>> vInline('Some text') 'Some text' >>> vInline.from_ical('Some text') 'Some text' >>> t2 = vInline('other text') >>> t2.params['cn'] = 'Test Osterone' >>> t2.params Parameters({'CN': 'Test Osterone'}) """ def __init__(self,obj): self.obj = obj self.params = Parameters() def ical(self): return str(self) def from_ical(ical): return str(ical) from_ical = staticmethod(from_ical) def __str__(self): return str(self.obj) class TypesFactory(CaselessDict): """ All Value types defined in rfc 2445 are registered in this factory class. To get a type you can use it like this. >>> factory = TypesFactory() >>> datetime_parser = factory['date-time'] >>> dt = datetime_parser(datetime(2001, 1, 1)) >>> dt.ical() '20010101T000000' A typical use is when the parser tries to find a content type and use text as the default >>> value = '20050101T123000' >>> value_type = 'date-time' >>> typ = factory.get(value_type, 'text') >>> typ.from_ical(value) datetime.datetime(2005, 1, 1, 12, 30) It can also be used to directly encode property and parameter values >>> comment = factory.ical('comment', u'by Rasmussen, Max Mller') >>> str(comment) 'by Rasmussen\\\\, Max M\\xc3\\xb8ller' >>> factory.ical('priority', 1) '1' >>> factory.ical('cn', u'Rasmussen, Max Mller') 'Rasmussen\\\\, Max M\\xc3\\xb8ller' >>> factory.from_ical('cn', 'Rasmussen\\\\, Max M\\xc3\\xb8ller') u'Rasmussen, Max M\\xf8ller' The value and parameter names don't overlap. So one factory is enough for both kinds. """ def __init__(self, *args, **kwargs): "Set keys to upper for initial dict" CaselessDict.__init__(self, *args, **kwargs) self['binary'] = vBinary self['boolean'] = vBoolean self['cal-address'] = vCalAddress self['date'] = vDDDTypes self['date-time'] = vDDDTypes self['duration'] = vDDDTypes self['float'] = vFloat self['integer'] = vInt self['period'] = vPeriod self['recur'] = vRecur self['text'] = vText self['time'] = vTime self['uri'] = vUri self['utc-offset'] = vUTCOffset self['geo'] = vGeo self['inline'] = vInline ################################################# # Property types # These are the default types types_map = CaselessDict({ #################################### # Property valye types # Calendar Properties 'calscale' : 'text', 'method' : 'text', 'prodid' : 'text', 'version' : 'text', # Descriptive Component Properties 'attach' : 'uri', 'categories' : 'text', 'class' : 'text', 'comment' : 'text', 'description' : 'text', 'geo' : 'geo', 'location' : 'text', 'percent-complete' : 'integer', 'priority' : 'integer', 'resources' : 'text', 'status' : 'text', 'summary' : 'text', # Date and Time Component Properties 'completed' : 'date-time', 'dtend' : 'date-time', 'due' : 'date-time', 'dtstart' : 'date-time', 'duration' : 'duration', 'freebusy' : 'period', 'transp' : 'text', # Time Zone Component Properties 'tzid' : 'text', 'tzname' : 'text', 'tzoffsetfrom' : 'utc-offset', 'tzoffsetto' : 'utc-offset', 'tzurl' : 'uri', # Relationship Component Properties 'attendee' : 'cal-address', 'contact' : 'text', 'organizer' : 'cal-address', 'recurrence-id' : 'date-time', 'related-to' : 'text', 'url' : 'uri', 'uid' : 'text', # Recurrence Component Properties 'exdate' : 'date-time', 'exrule' : 'recur', 'rdate' : 'date-time', 'rrule' : 'recur', # Alarm Component Properties 'action' : 'text', 'repeat' : 'integer', 'trigger' : 'duration', # Change Management Component Properties 'created' : 'date-time', 'dtstamp' : 'date-time', 'last-modified' : 'date-time', 'sequence' : 'integer', # Miscellaneous Component Properties 'request-status' : 'text', #################################### # parameter types (luckilly there is no name overlap) 'altrep' : 'uri', 'cn' : 'text', 'cutype' : 'text', 'delegated-from' : 'cal-address', 'delegated-to' : 'cal-address', 'dir' : 'uri', 'encoding' : 'text', 'fmttype' : 'text', 'fbtype' : 'text', 'language' : 'text', 'member' : 'cal-address', 'partstat' : 'text', 'range' : 'text', 'related' : 'text', 'reltype' : 'text', 'role' : 'text', 'rsvp' : 'boolean', 'sent-by' : 'cal-address', 'tzid' : 'text', 'value' : 'text', }) def for_property(self, name): "Returns a the default type for a property or parameter" return self[self.types_map.get(name, 'text')] def ical(self, name, value): """ Encodes a named value from a primitive python type to an icalendar encoded string. """ type_class = self.for_property(name) return type_class(value).ical() def from_ical(self, name, value): """ Decodes a named property or parameter value from an icalendar encoded string to a primitive python type. """ type_class = self.for_property(name) decoded = type_class.from_ical(str(value)) return decoded PKE?h3 /55icalendar/interfaces.pytry: from zope.interface import Interface, Attribute except ImportError: class Interface: """A dummy interface base class""" class Attribute: """A dummy attribute implementation""" def __init__(self, doc): self.doc = doc _marker = object() class IComponent(Interface): """ Component is the base object for calendar, Event and the other components defined in RFC 2445. A component is like a dictionary with extra methods and attributes. """ # MANIPULATORS def __setitem__(name, value): """Set a property. name - case insensitive name value - value of the property to set. This can be either a single item or a list. Some iCalendar properties are set INLINE; these properties have multiple values on one property line in the iCalendar representation. The list can be supplied as a comma separated string to __setitem__. If special iCalendar characters exist in an entry, such as the colon (:) and (,), that comma-separated entry needs to be quoted with double quotes. For example: 'foo, bar, "baz:hoi"' See also set_inline() for an easier way to deal with this case. """ def set_inline(name, values, encode=1): """Set list of INLINE values for property. Converts a list of values into valid iCalendar comma seperated string and sets value to that. name - case insensitive name of property values - list of values to set encode - if True, encode Python values as iCalendar types first. """ def add(name, value): """Add a property. Can be called multiple times to set a list. name - case insensitive name value - value of property to set or add to list for this property. """ def add_component(component): """Add a nested subcomponent to this component. """ # static method, can be called on class directly def from_string(st, multiple=False): """Populates the component recursively from a iCalendar string. Reads the iCalendar string and constructs components and subcomponents out of it. """ # ACCESSORS def __getitem__(name): """Get a property name - case insensitive name Returns an iCalendar property object such as vText. """ def decoded(name, default=_marker): """Get a property as a python object. name - case insensitive name default - optional argument. If supplied, will use this if name cannot be found. If not supplied, decoded will raise a KeyError if name cannot be found. Returns python object (such as unicode string, datetime, etc). """ def get_inline(name, decode=1): """Get list of INLINE values from property. name - case insensitive name decode - decode to Python objects. Returns list of python objects. """ def as_string(): """Render the component in the RFC 2445 (iCalendar) format. Returns a string in RFC 2445 format. """ subcomponents = Attribute(""" A list of all subcomponents of this component, added using add_component()""") name = Attribute(""" Name of this component (VEVENT, etc) """) def walk(name=None): """Recursively traverses component and subcomponents. name - optional, if given, only return components with that name Returns sequence of components. """ def property_items(): """Return properties as (name, value) tuples. Returns all properties in this comopnent and subcomponents as name, value tuples. """ class IEvent(IComponent): """A component which conforms to an iCalendar VEVENT. """ class ITodo(IComponent): """A component which conforms to an iCalendar VTODO. """ class IJournal(IComponent): """A component which conforms to an iCalendar VJOURNAL. """ class IFreeBusy(IComponent): """A component which conforms to an iCalendar VFREEBUSY. """ class ITimezone(IComponent): """A component which conforms to an iCalendar VTIMEZONE. """ class IAlarm(IComponent): """A component which conforms to an iCalendar VALARM. """ class ICalendar(IComponent): """A component which conforms to an iCalendar VCALENDAR. """ class IPropertyValue(Interface): """An iCalendar property value. iCalendar properties have strongly typed values. This invariance should always be true: assert x == vDataType.from_ical(vDataType(x).ical()) """ def ical(): """Render property as string, as defined in iCalendar RFC 2445. """ # this is a static method def from_ical(ical): """Parse property from iCalendar RFC 2445 text. Inverse of ical(). """ class IBinary(IPropertyValue): """Binary property values are base 64 encoded """ class IBoolean(IPropertyValue): """Boolean property. Also behaves like a python int. """ class ICalAddress(IPropertyValue): """Email address. Also behaves like a python str. """ class IDateTime(IPropertyValue): """Render and generates iCalendar datetime format. Important: if tzinfo is defined it renders itself as 'date with utc time' Meaning that it has a 'Z' appended, and is in absolute time. """ class IDate(IPropertyValue): """Render and generates iCalendar date format. """ class IDuration(IPropertyValue): """Render and generates timedelta in iCalendar DURATION format. """ class IFloat(IPropertyValue): """Render and generate floats in iCalendar format. Also behaves like a python float. """ class IInt(IPropertyValue): """Render and generate ints in iCalendar format. Also behaves like a python int. """ class IPeriod(IPropertyValue): """A precise period of time (datetime, datetime). """ class IWeekDay(IPropertyValue): """Render and generate weekday abbreviation. """ class IFrequency(IPropertyValue): """Frequency. """ class IRecur(IPropertyValue): """Render and generate data based on recurrent event representation. This acts like a caseless dictionary. """ class IText(IPropertyValue): """Unicode text. """ class ITime(IPropertyValue): """Time. """ class IUri(IPropertyValue): """URI """ class IGeo(IPropertyValue): """Geographical location. """ class IUTCOffset(IPropertyValue): """Offset from UTC. """ class IInline(IPropertyValue): """Inline list. """ PKC8āricalendar/prop.pyc; dEc@s dZdklZlZlZlZlZdklZlZl Z l Z l Z e e gZ dk Z dkZdklZdklZdZdZdeefZd Ze id eefZe id Zd fd YZdefdYZdefdYZedZeddZ edei! Z"ei#oedei$ Z%ne"Z%e%e"Z&defdYZ'defdYZ(e(Z(defdYZ)dfdYZ*dfdYZ+d fd!YZ,d"e-fd#YZ.d$efd%YZ/d&fd'YZ0d(fd)YZ1d*efd+YZ2d,efd-YZ3d.efd/YZ4d0e5fd1YZ6d2efd3YZ7d4efd5YZ8d6fd7YZ9d8fd9YZ:d:efd;YZ;d<efd=YZ<dS(>s This module contains the parser/generators (or coders/encoders if you prefer) for the classes/datatypes that are used in Icalendar: ########################################################################### # This module defines these property value data types and property parameters 4.2 Defined property parameters are: ALTREP, CN, CUTYPE, DELEGATED-FROM, DELEGATED-TO, DIR, ENCODING, FMTTYPE, FBTYPE, LANGUAGE, MEMBER, PARTSTAT, RANGE, RELATED, RELTYPE, ROLE, RSVP, SENT-BY, TZID, VALUE 4.3 Defined value data types are: BINARY, BOOLEAN, CAL-ADDRESS, DATE, DATE-TIME, DURATION, FLOAT, INTEGER, PERIOD, RECUR, TEXT, TIME, URI, UTC-OFFSET ########################################################################### iCalendar properties has values. The values are strongly typed. This module defines these types, calling val.ical() on them, Will render them as defined in rfc2445. If you pass any of these classes a Python primitive, you will have an object that can render itself as iCalendar formatted date. Property Value Data Types starts with a 'v'. they all have an ical() and from_ical() method. The ical() method generates a text string in the iCalendar format. The from_ical() method can parse this format and return a primitive Python datatype. So it should allways be true that: x == vDataType.from_ical(VDataType(x).ical()) These types are mainly used for parsing and file generation. But you can set them directly. (sdatetimes timedeltastimesdatestzinfo(sIntTypes StringTypes UnicodeTypes TupleTypesListTypeN(s CaselessDict(s Parameterss(\d+)Ds"T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?s(?:%s)?(?:%s)?s(\d+)Ws([-+]?)P(?:%s|%s)$s9(?P[+-]?)(?P[\d]?)(?P[\w]{2})$svBinarycBsGtZdZdZdZdZdZeeZdZRS(s+ Binary property values are base 64 encoded >>> b = vBinary('This is gibberish') >>> b.ical() 'VGhpcyBpcyBnaWJiZXJpc2g=' >>> b = vBinary.from_ical('VGhpcyBpcyBnaWJiZXJpc2g=') >>> b 'This is gibberish' The roundtrip test >>> x = 'Binary data  V' >>> vBinary(x).ical() 'QmluYXJ5IGRhdGEg5iD4IOUgEyBW' >>> vBinary.from_ical('QmluYXJ5IGRhdGEg5iD4IOUgEyBW') 'Binary data \xe6 \xf8 \xe5 \x13 V' >>> b = vBinary('txt') >>> b.params Parameters({'VALUE': 'BINARY', 'ENCODING': 'BASE64'}) cCs%||_tdddd|_dS(NsencodingsBASE64svaluesBINARY(sobjsselfs Parameterssparams(sselfsobj((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__Vs cCsdti|iSdS(Ns vBinary(%s)(sstrs__repr__sselfsobj(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__repr__ZscCs|iidd SdS(Nsbase-64i(sselfsobjsencode(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysical]scCs(y|idSWntdnXdS(s,Parses the data format from ical text formatsbase-64sNot valid base 64 encoding.N(sicalsdecodes ValueError(sical((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_ical`s cCs|iSdS(N(sselfsical(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__hs( s__name__s __module__s__doc__s__init__s__repr__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysvBinary@s      svBooleancBsStZdZdZdZededeZdZ e e Z dZ RS(s: Returns specific string according to state >>> bin = vBoolean(True) >>> bin.ical() 'TRUE' >>> bin = vBoolean(0) >>> bin.ical() 'FALSE' The roundtrip test >>> x = True >>> x == vBoolean.from_ical(vBoolean(x).ical()) True >>> vBoolean.from_ical('true') True cOs#ti|||t|_dS(N(sints__init__sselfsargsskwargss Parameterssparams(sselfsargsskwargs((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__scCs|odSndSdS(NsTRUEsFALSE(sself(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicalsstruesfalsecCs*yti|SWntd|nXdS(s,Parses the data format from ical text formats"Expected 'TRUE' or 'FALSE'. Got %sN(svBooleansbool_mapsicals ValueError(sical((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_icals cCs|iSdS(N(sselfsical(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__s( s__name__s __module__s__doc__s__init__sicals CaselessDictsTruesFalsesbool_maps from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysvBooleanms     s vCalAddresscBsGtZdZdZdZdZdZeeZdZRS(sM This just returns an unquoted string >>> a = vCalAddress('MAILTO:maxm@mxm.dk') >>> a.params['cn'] = 'Max M' >>> a.ical() 'MAILTO:maxm@mxm.dk' >>> str(a) 'MAILTO:maxm@mxm.dk' >>> a.params Parameters({'CN': 'Max M'}) >>> vCalAddress.from_ical('MAILTO:maxm@mxm.dk') 'MAILTO:maxm@mxm.dk' cOs#ti|||t|_dS(N(sstrs__init__sselfsargsskwargss Parameterssparams(sselfsargsskwargs((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__scCsdti|SdS(NuvCalAddress(%s)(sstrs__repr__sself(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__repr__scCst|SdS(N(sstrsself(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicalscCs)yt|SWntd|nXdS(s,Parses the data format from ical text formatsExpected vCalAddress, got: %sN(sstrsicals ValueError(sical((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_icals cCsti|SdS(N(sstrs__str__sself(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__s( s__name__s __module__s__doc__s__init__s__repr__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys vCalAddresss      ishoursissecondss FixedOffsetcBs2tZdZdZdZdZdZRS(s&Fixed offset in minutes east from UTC.cCstd||_||_dS(Nsminutes(s timedeltasoffsetsselfs_FixedOffset__offsetsnames_FixedOffset__name(sselfsoffsetsname((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__scCs |iSdS(N(sselfs_FixedOffset__offset(sselfsdt((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys utcoffsetscCs |iSdS(N(sselfs_FixedOffset__name(sselfsdt((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pystznamescCstSdS(N(sZERO(sselfsdt((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysdsts(s__name__s __module__s__doc__s__init__s utcoffsetstznamesdst(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys FixedOffsets    sUTCcBs)tZdZdZdZdZRS(sUTC tzinfo subclasscCstSdS(N(sZERO(sselfsdt((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys utcoffsetscCsdSdS(NsUTC((sselfsdt((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pystznamescCstSdS(N(sZERO(sselfsdt((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysdsts(s__name__s __module__s__doc__s utcoffsetstznamesdst(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysUTCs   s LocalTimezonecBs2tZdZdZdZdZdZRS(s; Timezone of the machine where the code is running cCs |i|otSntSdS(N(sselfs_isdstsdts DSTOFFSETs STDOFFSET(sselfsdt((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys utcoffsetscCs |i|otSntSdS(N(sselfs_isdstsdtsDSTDIFFsZERO(sselfsdt((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysdstscCsti|i|SdS(N(s_timestznamesselfs_isdstsdt(sselfsdt((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pystznamesc Csh|i|i|i|i|i|i|iddf }t i |}t i |}|i djSdS(Nii(sdtsyearsmonthsdayshoursminutessecondsweekdaystts_timesmktimesstamps localtimestm_isdst(sselfsdtsstampstt((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys_isdsts9(s__name__s __module__s__doc__s utcoffsetsdststznames_isdst(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys LocalTimezones    s vDatetimecBs>tZdZdZdZdZeeZdZRS(s` Render and generates iCalendar datetime format. Important: if tzinfo is defined it renders itself as "date with utc time" Meaning that it has a 'Z' appended, and is in absolute time. >>> d = datetime(2001, 1,1, 12, 30, 0) >>> dt = vDatetime(d) >>> dt.ical() '20010101T123000' >>> vDatetime.from_ical('20000101T120000') datetime.datetime(2000, 1, 1, 12, 0) >>> dutc = datetime(2001, 1,1, 12, 30, 0, tzinfo=UTC) >>> vDatetime(dutc).ical() '20010101T123000Z' >>> vDatetime.from_ical('20010101T000000') datetime.datetime(2001, 1, 1, 0, 0) >>> vDatetime.from_ical('20010101T000000A') Traceback (most recent call last): ... ValueError: Wrong datetime format: 20010101T000000A >>> utc = vDatetime.from_ical('20010101T000000Z') >>> vDatetime(utc).ical() '20010101T000000Z' cCs||_t|_dS(N(sdtsselfs Parameterssparams(sselfsdt((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__'s cCso|iioN|iiiti}|i|iiiti}|idSn|iidSdS(Ns%Y%m%dT%H%M%SZs %Y%m%dT%H%M%S( sselfsdtstzinfos utcoffsetsdatetimesnowsoffsetsutc_timesstrftime(sselfsutc_timesoffset((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysical+s  "c Csytt|d |dd!|dd!|dd!|dd!|dd!f}|d ot|Sn<|dd!d jo|d tg7}t|Sn t|Wntd |nXd S( s,Parses the data format from ical text formatiiii i i iisZisWrong datetime format: %sN(smapsintsicals timetuplesdatetimesUTCs ValueError(sicals timetuple((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_ical2sH  cCs|iSdS(N(sselfsical(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__Hs(s__name__s __module__s__doc__s__init__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys vDatetimes     svDatecBs>tZdZdZdZdZeeZdZRS(s7 Render and generates iCalendar date format. >>> d = date(2001, 1,1) >>> vDate(d).ical() '20010101' >>> vDate.from_ical('20010102') datetime.date(2001, 1, 2) >>> vDate('d').ical() Traceback (most recent call last): ... ValueError: Value MUST be a date instance cCs:t|t otdn||_t|_dS(NsValue MUST be a date instance(s isinstancesdtsdates ValueErrorsselfs Parameterssparams(sselfsdt((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__]s cCs|iidSdS(Ns%Y%m%d(sselfsdtsstrftime(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicalcscCsSy8tt|d |dd!|dd!f}t|SWntd|nXdS(s,Parses the data format from ical text formatiiisWrong date format %sN(smapsintsicals timetuplesdates ValueError(sicals timetuple((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_icalfs *cCs|iSdS(N(sselfsical(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__ss(s__name__s __module__s__doc__s__init__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysvDateMs    s vDurationcBs>tZdZdZdZdZeeZdZRS(sB Subclass of timedelta that renders itself in the iCalendar DURATION format. >>> vDuration(timedelta(11)).ical() 'P11D' >>> vDuration(timedelta(-14)).ical() '-P14D' >>> vDuration(timedelta(1, 7384)).ical() 'P1DT2H3M4S' >>> vDuration(timedelta(1, 7380)).ical() 'P1DT2H3M' >>> vDuration(timedelta(1, 7200)).ical() 'P1DT2H' >>> vDuration(timedelta(0, 7200)).ical() 'PT2H' >>> vDuration(timedelta(0, 7384)).ical() 'PT2H3M4S' >>> vDuration(timedelta(0, 184)).ical() 'PT3M4S' >>> vDuration(timedelta(0, 22)).ical() 'PT22S' >>> vDuration(timedelta(0, 3622)).ical() 'PT1H0M22S' How does the parsing work? >>> vDuration.from_ical('PT1H0M22S') datetime.timedelta(0, 3622) >>> vDuration.from_ical('kox') Traceback (most recent call last): ... ValueError: Invalid iCalendar duration: kox >>> vDuration.from_ical('-P14D') datetime.timedelta(-14) >>> vDuration(11) Traceback (most recent call last): ... ValueError: Value MUST be a timedelta instance cCs:t|t otdn||_t|_dS(Ns"Value MUST be a timedelta instance(s isinstancestds timedeltas ValueErrorsselfs Parameterssparams(sselfstd((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__s cCsd}|iidjo d}nd}|iiod}|iid}|iidd}|iid}|o|d|7}n|p |o|o|d|7}n|o|d |7}qn|iidjo|od ||fSnd |t|ii|fSdS( Nsis-sTii<s%dHs%dMs%dSs%sP%ss%sP%dD%s( ssignsselfstdsdaysstimepartssecondsshourssminutessabs(sselfstimepartssignshoursssecondssminutes((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicals$  c Csyti|}|i\}}}}}}|ot dt |}nVt dt |pddt |pddt |pddt |pd}|djo | }n|SWnt d|nXd S( s? Parses the data format from ical text format. sweekssdaysishourssminutesssecondss-sInvalid iCalendar duration: %sN(sDURATION_REGEXsmatchsicalsgroupsssignsweekssdaysshourssminutesssecondss timedeltasintsvalues ValueError( sicalssecondssdaysssignshourssvaluesweekssminutessmatch((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_icals  cCs|iSdS(N(sselfsical(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__s(s__name__s __module__s__doc__s__init__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys vDurationxs )    svFloatcBs5tZdZdZdZdZeeZRS(s Just a float. >>> f = vFloat(1.0) >>> f.ical() '1.0' >>> vFloat.from_ical('42') 42.0 >>> vFloat(42).ical() '42.0' cOs#ti|||t|_dS(N(sfloats__init__sselfsargsskwargss Parameterssparams(sselfsargsskwargs((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__scCst|SdS(N(sstrsself(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicalscCs)yt|SWntd|nXdS(s,Parses the data format from ical text formatsExpected float value, got: %sN(sfloatsicals ValueError(sical((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_icals (s__name__s __module__s__doc__s__init__sicals from_icals staticmethod(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysvFloats    svIntcBs5tZdZdZdZdZeeZRS(s Just an int. >>> f = vInt(42) >>> f.ical() '42' >>> vInt.from_ical('13') 13 >>> vInt.from_ical('1s3') Traceback (most recent call last): ... ValueError: Expected int, got: 1s3 cOs#ti|||t|_dS(N(sints__init__sselfsargsskwargss Parameterssparams(sselfsargsskwargs((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__scCst|SdS(N(sstrsself(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicalscCs)yt|SWntd|nXdS(s,Parses the data format from ical text formatsExpected int, got: %sN(sintsicals ValueError(sical((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_ical s (s__name__s __module__s__doc__s__init__sicals from_icals staticmethod(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysvInts    s vDDDTypescBs>tZdZdZdZdZeeZdZRS(s A combined Datetime, Date or Duration parser/generator. Their format cannot be confused, and often values can be of either types. So this is practical. >>> d = vDDDTypes.from_ical('20010101T123000') >>> type(d) >>> repr(vDDDTypes.from_ical('20010101T123000Z'))[:65] 'datetime.datetime(2001, 1, 1, 12, 30, tzinfo=>> d = vDDDTypes.from_ical('20010101') >>> type(d) >>> vDDDTypes.from_ical('P31D') datetime.timedelta(31) >>> vDDDTypes.from_ical('-P31D') datetime.timedelta(-31) Bad input >>> vDDDTypes(42) Traceback (most recent call last): ... ValueError: You must use datetime, date or timedelta cCs^d}x1tttfD] }t||o d}qqW|otdn||_dS(sReturns vDate fromiis(You must use datetime, date or timedeltaN( swrong_type_usedsdatetimesdates timedeltastyps isinstancesdts ValueErrorsself(sselfsdtswrong_type_usedstyp((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__1scCs|i}t|tot|iSnUt|tot|iSn1t|tot |iSn t ddS(NsUnknown date type( sselfsdts isinstancesdatetimes vDatetimesicalsdatesvDates timedeltas vDurations ValueEror(sselfsdt((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysical;s cCsi|i}|idp |idoti|Snyti|SWnti|SnXdS(s,Parses the data format from ical text formats-PsPN(sicalsuppersus startswiths vDurations from_icals vDatetimesvDate(sicalsu((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_icalFs  cCs|iSdS(N(sselfsical(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__Qs(s__name__s __module__s__doc__s__init__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys vDDDTypess  svPeriodcBsYtZdZdZdZdZdZdZeeZdZ dZ RS(s~ A precise period of time. One day in exact datetimes >>> per = (datetime(2000,1,1), datetime(2000,1,2)) >>> p = vPeriod(per) >>> p.ical() '20000101T000000/20000102T000000' >>> per = (datetime(2000,1,1), timedelta(days=31)) >>> p = vPeriod(per) >>> p.ical() '20000101T000000/P31D' Roundtrip >>> p = vPeriod.from_ical('20000101T000000/20000102T000000') >>> p (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 1, 2, 0, 0)) >>> vPeriod(p).ical() '20000101T000000/20000102T000000' >>> vPeriod.from_ical('20000101T000000/P31D') (datetime.datetime(2000, 1, 1, 0, 0), datetime.timedelta(31)) Roundtrip with absolute time >>> p = vPeriod.from_ical('20000101T000000Z/20000102T000000Z') >>> vPeriod(p).ical() '20000101T000000Z/20000102T000000Z' And an error >>> vPeriod.from_ical('20000101T000000/Psd31D') Traceback (most recent call last): ... ValueError: Expected period format, got: 20000101T000000/Psd31D Utc datetime >>> da_tz = FixedOffset(+1.0, 'da_DK') >>> start = datetime(2000,1,1, tzinfo=da_tz) >>> end = datetime(2000,1,2, tzinfo=da_tz) >>> per = (start, end) >>> vPeriod(per).ical() '19991231T235900Z/20000101T235900Z' >>> p = vPeriod((datetime(2000,1,1, tzinfo=da_tz), timedelta(days=31))) >>> p.ical() '19991231T235900Z/P31D' cCs!|\}}t|tp t|t otdnt|tpt|tp t|t otdn||_||_d|_ t|to)d|_ ||_ |i|i |_ n||_ |i |i|_ |i|i jotdnt |_ dS(Ns/Start value MUST be a datetime or date instances>end_or_duration MUST be a datetime, date or timedelta instanceiis#Start time is greater than end time(spersstartsend_or_durations isinstancesdatetimesdates ValueErrors timedeltasselfs by_durationsdurationsends Parameterssparams(sselfspersend_or_durationsstart((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__s" !1      cCsTt|t otdt|nt|i|if|i|ifSdS(NsCannot compare vPeriod with %s( s isinstancesothersvPeriodsNotImplementedErrorsreprscmpsselfsstartsend(sselfsother((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__cmp__scCs[|i|ijo|i|Sn|i|ijo |ijnotSntSdS(N(sselfsstartsothersoverlapssendsTruesFalse(sselfsother((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysoverlapss 'cCsj|io0dt|iit|iifSndt|iit|iifSdS(Ns%s/%s(sselfs by_durations vDatetimesstartsicals vDurationsdurationsend(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicals 0cCs\yA|id\}}ti|}ti|}||fSWntd|nXdS(s,Parses the data format from ical text formats/sExpected period format, got: %sN(sicalssplitsstartsend_or_durations vDDDTypess from_icals ValueError(sicalsstartsend_or_duration((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_icalscCs|iSdS(N(sselfsical(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__scCsD|io|i|if}n|i|if}dt|SdS(Ns vPeriod(%s)(sselfs by_durationsstartsdurationspsendsrepr(sselfsp((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__repr__s ( s__name__s __module__s__doc__s__init__s__cmp__soverlapssicals from_icals staticmethods__str__s__repr__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysvPeriodVs .      svWeekdaycBstZdZehdd<dd<dd<dd<d d <d d <d d<ZdZdZdZeeZdZ RS(s This returns an unquoted weekday abbrevation >>> a = vWeekday('mo') >>> a.ical() 'MO' >>> a = vWeekday('erwer') Traceback (most recent call last): ... ValueError: Expected weekday abbrevation, got: ERWER >>> vWeekday.from_ical('mo') 'MO' >>> vWeekday.from_ical('+3mo') '+3MO' >>> vWeekday.from_ical('Saturday') Traceback (most recent call last): ... ValueError: Expected weekday abbrevation, got: Saturday >>> a = vWeekday('+mo') >>> a.ical() '+MO' >>> a = vWeekday('+3mo') >>> a.ical() '+3MO' >>> a = vWeekday('-tu') >>> a.ical() '-TU' sSUisMOisTUisWEisTHisFRisSAicOsti|||ti|}|tjotd|n|i }|d}|d}|d}|t ij p |djotd|n|o t|pt|_ t|_dS(Ns%Expected weekday abbrevation, got: %sssignalsweekdaysrelatives+-(sstrs__init__sselfsargsskwargss WEEKDAY_RULEsmatchsNones ValueErrors groupdictssignsweekdaysrelativesvWeekdays week_dayssints Parameterssparams(sselfsargsskwargsssignsrelativesweekdaysmatch((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__s     cCs|iSdS(N(sselfsupper(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicalscCs/yt|iSWntd|nXdS(s,Parses the data format from ical text formats%Expected weekday abbrevation, got: %sN(svWeekdaysicalsuppers ValueError(sical((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_icals cCs|iSdS(N(sselfsical(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__s( s__name__s __module__s__doc__s CaselessDicts week_dayss__init__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysvWeekdays "K    s vFrequencycBstZdZehdd<dd<dd<dd<dd<dd<dd<ZdZd Zd ZeeZd Z RS( s+ A simple class that catches illegal values. >>> f = vFrequency('bad test') Traceback (most recent call last): ... ValueError: Expected frequency, got: BAD TEST >>> vFrequency('daily').ical() 'DAILY' >>> vFrequency('daily').from_ical('MONTHLY') 'MONTHLY' sSECONDLYsMINUTELYsHOURLYsDAILYsWEEKLYsMONTHLYsYEARLYcOsEti||||tij otd|nt|_ dS(NsExpected frequency, got: %s( sstrs__init__sselfsargsskwargss vFrequencys frequenciess ValueErrors Parameterssparams(sselfsargsskwargs((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__scCs|iSdS(N(sselfsupper(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysical%scCs/yt|iSWntd|nXdS(s,Parses the data format from ical text formats%Expected weekday abbrevation, got: %sN(s vFrequencysicalsuppers ValueError(sical((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_ical(s cCs|iSdS(N(sselfsical(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__0s( s__name__s __module__s__doc__s CaselessDicts frequenciess__init__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys vFrequencys K    svRecurcBstZdZdddddddgZehde<d e<d e<d e<d e<d e<de<de<de<de<de<de<de<Z dZ dZ dZ e e Z dZe eZdZRS(s Let's see how close we can get to one from the rfc: FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30 >>> r = dict(freq='yearly', interval=2) >>> r['bymonth'] = 1 >>> r['byday'] = 'su' >>> r['byhour'] = [8,9] >>> r['byminute'] = 30 >>> r = vRecur(r) >>> r.ical() 'BYHOUR=8,9;BYDAY=SU;BYMINUTE=30;BYMONTH=1;FREQ=YEARLY;INTERVAL=2' >>> r = vRecur(FREQ='yearly', INTERVAL=2) >>> r['BYMONTH'] = 1 >>> r['BYDAY'] = 'su' >>> r['BYHOUR'] = [8,9] >>> r['BYMINUTE'] = 30 >>> r.ical() 'BYDAY=SU;BYMINUTE=30;BYMONTH=1;INTERVAL=2;FREQ=YEARLY;BYHOUR=8,9' >>> r = vRecur(freq='DAILY', count=10) >>> r['bysecond'] = [0, 15, 30, 45] >>> r.ical() 'COUNT=10;FREQ=DAILY;BYSECOND=0,15,30,45' >>> r = vRecur(freq='DAILY', until=datetime(2005,1,1,12,0,0)) >>> r.ical() 'FREQ=DAILY;UNTIL=20050101T120000' How do we fare with regards to parsing? >>> r = vRecur.from_ical('FREQ=DAILY;INTERVAL=2;COUNT=10') >>> r {'COUNT': [10], 'FREQ': ['DAILY'], 'INTERVAL': [2]} >>> vRecur(r).ical() 'COUNT=10;FREQ=DAILY;INTERVAL=2' >>> r = vRecur.from_ical('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=-SU;BYHOUR=8,9;BYMINUTE=30') >>> r {'BYHOUR': [8, 9], 'BYDAY': ['-SU'], 'BYMINUTE': [30], 'BYMONTH': [1], 'FREQ': ['YEARLY'], 'INTERVAL': [2]} >>> vRecur(r).ical() 'BYDAY=-SU;BYMINUTE=30;INTERVAL=2;BYMONTH=1;FREQ=YEARLY;BYHOUR=8,9' Some examples from the spec >>> r = vRecur.from_ical('FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1') >>> vRecur(r).ical() 'BYSETPOS=-1;FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR' >>> r = vRecur.from_ical('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30') >>> vRecur(r).ical() 'BYDAY=SU;BYMINUTE=30;INTERVAL=2;BYMONTH=1;FREQ=YEARLY;BYHOUR=8,9' and some errors >>> r = vRecur.from_ical('BYDAY=12') Traceback (most recent call last): ... ValueError: Error in recurrence rule: BYDAY=12 sSECONDLYsMINUTELYsHOURLYsDAILYsWEEKLYsMONTHLYsYEARLYsCOUNTsINTERVALsBYSECONDsBYMINUTEsBYHOURs BYMONTHDAYs BYYEARDAYsBYMONTHsUNTILsBYSETPOSsWKSTsBYDAYsFREQcOs#ti|||t|_dS(N(s CaselessDicts__init__sselfsargsskwargss Parameterssparams(sselfsargsskwargs((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__scCsg}x|iD]\}}|i|}t|tj o |g}ndi gi }|D]}|||i qa~}|i d||fqWdi |SdS(Ns,s%s=%ss;(sresultsselfsitemsskeysvalsstypesstypstypes SequenceTypessjoinsappends_[1]svalsical(sselfsvalstyps_[1]sresultskeysvals((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicals   <cCsPtii|t}gi}|i dD]}||i |q,~SdS(Ns,( svRecurstypessgetskeysvTextsparsersappends_[1]svaluesssplitsvs from_ical(skeysvaluessvsparsers_[1]((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys parse_typescCswy\t}xB|idD]1}|id\}}ti||||>> t = vText(u'Simple text') >>> t.ical() 'Simple text' Escaped text >>> t = vText('Text ; with escaped, chars') >>> t.ical() 'Text \\; with escaped\\, chars' Escaped newlines >>> vText('Text with escaped\N chars').ical() 'Text with escaped\\n chars' If you pass a unicode object, it will be utf-8 encoded. As this is the (only) standard that RFC 2445 support. >>> t = vText(u'international chars ') >>> t.ical() 'international chars \xc3\xa6\xc3\xb8\xc3\xa5 \xc3\x86\xc3\x98\xc3\x85 \xc3\xbc' Unicode is converted to utf-8 >>> t = vText(u'international ') >>> str(t) 'international \xc3\xa6 \xc3\xb8 \xc3\xa5' and parsing? >>> vText.from_ical('Text \; with escaped\, chars') u'Text ; with escaped, chars' >>> print vText.from_ical('A string with\; some\\ characters in\Nit') A string with; some\ characters in it sutf-8cOs#ti|||t|_dS(N(sunicodes__init__sselfsargsskwargss Parameterssparams(sselfsargsskwargs((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__scCsP|iddiddiddiddid d idd Sd S( sJ Format value according to iCalendar TEXT escaping rules. s\Ns s\s\\s;s\;s,s\,s s\nN(sselfsreplace(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysescapescCsdti|SdS(Nu vText(%s)(sunicodes__repr__sself(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__repr__scCs|ii|iSdS(N(sselfsescapesencodesencoding(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicalscCs}yb|iddiddiddiddiddid d }|itiSWntd |nXd S( s,Parses the data format from ical text formats\Ns\ns\r\ns s\,s,s\;s;s\\s\sExpected ical text, got: %sN(sicalsreplacesdecodesvTextsencodings ValueError(sical((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_icals NcCs|iSdS(N(sselfsical(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__s( s__name__s __module__s__doc__sencodings__init__sescapes__repr__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysvTexts #    svTimecBs>tZdZdZdZdZeeZdZRS(sr A subclass of datetime, that renders itself in the iCalendar time format. >>> dt = vTime(12, 30, 0) >>> dt.ical() '123000' >>> vTime.from_ical('123000') datetime.time(12, 30) We should also fail, right? >>> vTime.from_ical('263000') Traceback (most recent call last): ... ValueError: Expected time, got: 263000 cOs#ti|||t|_dS(N(stimes__init__sselfsargsskwargss Parameterssparams(sselfsargsskwargs((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__scCs|idSdS(Ns%H%M%S(sselfsstrftime(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicalscCsSy8tt|d |dd!|dd!f}t|SWntd|nXdS(s,Parses the data format from ical text formatiiisExpected time, got: %sN(smapsintsicals timetuplestimes ValueError(sicals timetuple((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_icals *cCs|iSdS(N(sselfsical(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__s(s__name__s __module__s__doc__s__init__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysvTimes     svUricBs>tZdZdZdZdZeeZdZRS(s Uniform resource identifier is basically just an unquoted string. >>> u = vUri('http://www.example.com/') >>> u.ical() 'http://www.example.com/' >>> vUri.from_ical('http://www.example.com/') # doh! 'http://www.example.com/' cOs#ti|||t|_dS(N(sstrs__init__sselfsargsskwargss Parameterssparams(sselfsargsskwargs((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__-scCst|SdS(N(sstrsself(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysical1scCs)yt|SWntd|nXdS(s,Parses the data format from ical text formatsExpected , got: %sN(sstrsicals ValueError(sical((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_ical4s cCsti|SdS(N(sstrs__str__sself(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__<s(s__name__s __module__s__doc__s__init__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysvUri#s     svGeocBs>tZdZdZdZdZeeZdZRS(s A special type that is only indirectly defined in the rfc. >>> g = vGeo((1.2, 3.0)) >>> g.ical() '1.2;3.0' >>> g = vGeo.from_ical('37.386013;-122.082932') >>> g (37.386012999999998, -122.082932) >>> vGeo(g).ical() '37.386013;-122.082932' >>> vGeo('g').ical() Traceback (most recent call last): ... ValueError: Input must be (float, float) for latitude and longitude cCs`y(|\}}t|}t|}WntdnX||_||_t|_dS(Ns7Input must be (float, float) for latitude and longitude(sgeoslatitudes longitudesfloats ValueErrorsselfs Parameterssparams(sselfsgeos longitudeslatitude((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__Vs    cCsd|i|ifSdS(Ns%s;%s(sselfslatitudes longitude(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicalascCsJy/|id\}}t|t|fSWntd|nXdS(s,Parses the data format from ical text formats;s Expected 'float;float' , got: %sN(sicalssplitslatitudes longitudesfloats ValueError(sicalslatitudes longitude((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_icalds cCs|iSdS(N(sselfsical(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__ms(s__name__s __module__s__doc__s__init__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysvGeoAs    s vUTCOffsetcBs>tZdZdZdZdZeeZdZRS(s] Renders itself as a utc offset >>> u = vUTCOffset(timedelta(hours=2)) >>> u.ical() '+0200' >>> u = vUTCOffset(timedelta(hours=-5)) >>> u.ical() '-0500' >>> u = vUTCOffset(timedelta()) >>> u.ical() '0000' >>> u = vUTCOffset(timedelta(minutes=-30)) >>> u.ical() '-0030' >>> u = vUTCOffset(timedelta(hours=2, minutes=-30)) >>> u.ical() '+0130' >>> u = vUTCOffset(timedelta(hours=1, minutes=30)) >>> u.ical() '+0130' Parsing >>> vUTCOffset.from_ical('0000') datetime.timedelta(0) >>> vUTCOffset.from_ical('-0030') datetime.timedelta(-1, 84600) >>> vUTCOffset.from_ical('+0200') datetime.timedelta(0, 7200) >>> o = vUTCOffset.from_ical('+0230') >>> vUTCOffset(o).ical() '+0230' And a few failures >>> vUTCOffset.from_ical('+323k') Traceback (most recent call last): ... ValueError: Expected utc offset, got: +323k >>> vUTCOffset.from_ical('+2400') Traceback (most recent call last): ... ValueError: Offset must be less than 24 hours, was +2400 cCs:t|t otdn||_t|_dS(Ns)Offset value MUST be a timedelta instance(s isinstancestds timedeltas ValueErrorsselfs Parameterssparams(sselfstd((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__s c Cs|i}|idd}|id}||}|djo d}n|djo d}nd}t|d}|d}d||f}||SdS(Nii<is%ss-%ss+%ss%02i%02i( sselfstdsdayssday_in_minutesssecondssseconds_in_minutess total_minutesssignsabsshourssminutessduration( sselfsday_in_minutesssignshourss total_minutessseconds_in_minutessdurationstdsminutes((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicals        cCsyO|dd!t|dd!t|df\}}}td|d|}Wntd|nX|tddjotd|n|d jo | Sn|Sd S( s,Parses the data format from ical text formatiiishourssminutessExpected utc offset, got: %sis)Offset must be less than 24 hours, was %ss-N(sicalsintssignshourssminutess timedeltasoffsets ValueError(sicalssignshourssoffsetsminutes((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_icals6  cCs|iSdS(N(sselfsical(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__s(s__name__s __module__s__doc__s__init__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys vUTCOffsetrs 5   svInlinecBs>tZdZdZdZdZeeZdZRS(s This is an especially dumb class that just holds raw unparsed text and has parameters. Conversion of inline values are handled by the Component class, so no further processing is needed. >>> vInline('Some text') 'Some text' >>> vInline.from_ical('Some text') 'Some text' >>> t2 = vInline('other text') >>> t2.params['cn'] = 'Test Osterone' >>> t2.params Parameters({'CN': 'Test Osterone'}) cCs||_t|_dS(N(sobjsselfs Parameterssparams(sselfsobj((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__init__s cCst|SdS(N(sstrsself(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysicalscCst|SdS(N(sstrsical(sical((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys from_icalscCst|iSdS(N(sstrsselfsobj(sself((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pys__str__s(s__name__s __module__s__doc__s__init__sicals from_icals staticmethods__str__(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/prop.pysvInlines     s TypesFactorycBstZdZdZehdd<dd<dd<dd<dd<d d<d d<d d<d d<d d <dd<dd<dd<dd<dd<dd<dd<dd<dd<dd<dd<dd<dd<dd<dd<d d!<d"d!<d#d<d$d%<d&d<d'd%<d(d<d)d<d*d<d+d<d,d<d-d.<d/d<d0d.<d1d<d2d<d3d<d4d<d5d<d6d<d7d<d8d<d9d<d:d<d;d<d<d%<d=d%<d>d<d?d<d@d<dAd<dBd<dCd%<dDd<dEd<dFd<dGd<dHd<dIdJ<dKd%<dd<dLd<ZdMZdNZdOZRS(Ps[ All Value types defined in rfc 2445 are registered in this factory class. To get a type you can use it like this. >>> factory = TypesFactory() >>> datetime_parser = factory['date-time'] >>> dt = datetime_parser(datetime(2001, 1, 1)) >>> dt.ical() '20010101T000000' A typical use is when the parser tries to find a content type and use text as the default >>> value = '20050101T123000' >>> value_type = 'date-time' >>> typ = factory.get(value_type, 'text') >>> typ.from_ical(value) datetime.datetime(2005, 1, 1, 12, 30) It can also be used to directly encode property and parameter values >>> comment = factory.ical('comment', u'by Rasmussen, Max Mller') >>> str(comment) 'by Rasmussen\\, Max M\xc3\xb8ller' >>> factory.ical('priority', 1) '1' >>> factory.ical('cn', u'Rasmussen, Max Mller') 'Rasmussen\\, Max M\xc3\xb8ller' >>> factory.from_ical('cn', 'Rasmussen\\, Max M\xc3\xb8ller') u'Rasmussen, Max M\xf8ller' The value and parameter names don't overlap. So one factory is enough for both kinds. cOsti|||t|d>> factory = ComponentFactory() >>> component = factory['VEVENT'] >>> event = component(dtstart='19700101') >>> event.as_string() 'BEGIN:VEVENT\r\nDTSTART:19700101\r\nEND:VEVENT\r\n' >>> factory.get('VCALENDAR', Component) cOs]ti|||t|d>> c = Component() >>> c.name = 'VCALENDAR' Every key defines a property. A property can consist of either a single item. This can be set with a single value >>> c['prodid'] = '-//max m//icalendar.mxm.dk/' >>> c VCALENDAR({'PRODID': '-//max m//icalendar.mxm.dk/'}) or with a list >>> c['ATTENDEE'] = ['Max M', 'Rasmussen'] if you use the add method you don't have to considder if a value is a list or not. >>> c = Component() >>> c.name = 'VEVENT' >>> c.add('attendee', 'maxm@mxm.dk') >>> c.add('attendee', 'test@example.dk') >>> c VEVENT({'ATTENDEE': [vCalAddress('maxm@mxm.dk'), vCalAddress('test@example.dk')]}) You can get the values back directly >>> c.add('prodid', '-//my product//') >>> c['prodid'] vText(u'-//my product//') or decoded to a python type >>> c.decoded('prodid') u'-//my product//' With default values for non existing properties >>> c.decoded('version', 'No Version') 'No Version' The component can render itself in the RFC 2445 format. >>> c = Component() >>> c.name = 'VCALENDAR' >>> c.add('attendee', 'Max M') >>> c.as_string() 'BEGIN:VCALENDAR\r\nATTENDEE:Max M\r\nEND:VCALENDAR\r\n' >>> from icalendar.prop import vDatetime Components can be nested, so You can add a subcompont. Eg a calendar holds events. >>> e = Component(summary='A brief history of time') >>> e.name = 'VEVENT' >>> e.add('dtend', '20000102T000000', encode=0) >>> e.add('dtstart', '20000101T000000', encode=0) >>> e.as_string() 'BEGIN:VEVENT\r\nDTEND:20000102T000000\r\nDTSTART:20000101T000000\r\nSUMMARY:A brief history of time\r\nEND:VEVENT\r\n' >>> c.add_component(e) >>> c.subcomponents [VEVENT({'DTEND': '20000102T000000', 'DTSTART': '20000101T000000', 'SUMMARY': 'A brief history of time'})] We can walk over nested componentes with the walk method. >>> [i.name for i in c.walk()] ['VCALENDAR', 'VEVENT'] We can also just walk over specific component types, by filtering them on their name. >>> [i.name for i in c.walk('VEVENT')] ['VEVENT'] >>> [i['dtstart'] for i in c.walk('VEVENT')] ['20000101T000000'] INLINE properties have their values on one property line. Note the double quoting of the value with a colon in it. >>> c = Calendar() >>> c['resources'] = 'Chair, Table, "Room: 42"' >>> c VCALENDAR({'RESOURCES': 'Chair, Table, "Room: 42"'}) >>> c.as_string() 'BEGIN:VCALENDAR\r\nRESOURCES:Chair, Table, "Room: 42"\r\nEND:VCALENDAR\r\n' The inline values must be handled by the get_inline() and set_inline() methods. >>> c.get_inline('resources', decode=0) ['Chair', 'Table', 'Room: 42'] These can also be decoded >>> c.get_inline('resources', decode=1) [u'Chair', u'Table', u'Room: 42'] You can set them directly >>> c.set_inline('resources', ['A', 'List', 'of', 'some, recources'], encode=1) >>> c['resources'] 'A,List,of,"some, recources"' and back again >>> c.get_inline('resources', decode=0) ['A', 'List', 'of', 'some, recources'] >>> c['freebusy'] = '19970308T160000Z/PT3H,19970308T200000Z/PT1H,19970308T230000Z/19970309T000000Z' >>> c.get_inline('freebusy', decode=0) ['19970308T160000Z/PT3H', '19970308T200000Z/PT1H', '19970308T230000Z/19970309T000000Z'] >>> freebusy = c.get_inline('freebusy', decode=1) >>> type(freebusy[0][0]), type(freebusy[0][1]) (, ) scOs ti|||g|_dS(s"Set keys to upper for initial dictN(s CaselessDicts__init__sselfsargsskwargss subcomponents(sselfsargsskwargs((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pys__init__sicCs,|oti|}||Sn|SdS(N(sconds types_factorys for_propertysnamesklasssvalue(sselfsnamesvaluescondsklass((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pys_encodescCsnt|tjo>gi}|D]}||i|||q!~||cCs||joc||}|i|||}t|tjo|i|q|i |||gddn|i |||dS(s1If property exists append, else create and set itsencodeiN( snamesselfsoldvals_encodesvaluesencodestypesListTypesappendsset(sselfsnamesvaluesencodesoldval((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pysadds   cCsti||}|SdS(N(s types_factorys from_icalsnamesvaluesdecoded(sselfsnamesvaluesdecoded((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pys_decodescCs||jof||}t|tjo5gi}|D]}||i||q8~Sn|i||Sn|t jo t |n|SdS(s!Returns decoded value of propertyN( snamesselfsvaluestypesListTypesappends_[1]svs_decodesdefaults_markersKeyError(sselfsnamesdefaultsvalues_[1]sv((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pysdecodeds  5  cCsgi}t||D]%}||iditi q~}|o5gi}|D]}||i ||q[~Sn|SdS(s< Returns a list of values (split on comma). s" N(sappends_[1]sq_splitsselfsnamesvsstripsencodesvTextsencodingsvalssdecodesvals_decode(sselfsnamesdecodes_[1]svalsvsvals((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pys get_inlines F5cCsq|o:gi}|D]}||i||dq~}nt|it i }t d|||W|SdS(N(sresultsnamesNonesselfsappends subcomponentss subcomponents_walk(sselfsnames subcomponentsresult((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pys_walk!s cCs/|tj o|i}n|i|SdS(s Recursively traverses component and subcomponents. Returns sequence of same. If name is passed, only components with name will be returned. N(snamesNonesuppersselfs_walk(sselfsname((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pyswalk+scCstd}d||iifg}|i}|ixf|D]^}||}t |t jo(x8|D]}|i ||fqoWqE|i ||fqEWx!|iD]}||i7}qW|i d||iif|SdS(si Returns properties in this component and subcomponents as: [(name, value), ...] stextsBEGINsENDN(s types_factorysvTextsselfsnamesicals propertiesskeyssproperty_namesssortsvaluesstypesListTypesvaluesappends subcomponentss subcomponentsproperty_items(sselfsproperty_namessnamesvaluesvaluessvTexts subcomponents properties((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pysproperty_items7s$     "c Csg} g}x;ti|D]*} | oqn| i\}}} |i }|djoY| i } t i| t}|}t|dd o | |_n| i|q|djo:| i}| o|i|qF| di|qti|}||i| } || _| di|| ddqW|o|Snt|dj otd n|dSd S( sC Populates the component recursively from a string sBEGINsnamessENDisencodeiis3Found multiple components where only one is allowedN(sstackscompss Contentliness from_stringsstslinespartssnamesparamssvalssuppersunamescomponent_namescomponent_factorysgets Componentscomponent_classs componentsgetattrsappendspops add_components types_factorys for_propertysfactorys from_icalsaddsmultipleslens ValueError( sstsmultiplesnamesunames componentscomponent_classsfactoryscompssparamsslinesvalssstackscomponent_name((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pys from_stringOs<        cCs d|iti|dSdS(Ns%s(s)(sselfsnamesdicts__repr__(sself((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pys__repr__|scCsot}xN|iD]@\}}t|dt}|i t i |||fqW|i d|SdS(s;Converts the Component and subcomponents into content linessparamssN( s Contentliness contentlinessselfsproperty_itemssnamesvaluessgetattrs Parameterssparamssappends Contentlines from_parts(sselfsnamesvaluess contentlinessparams((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pys content_liness  # cCst|iSdS(N(sstrsselfs content_lines(sself((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pys as_stringscCs|iSdS(sReturns rendered iCalendarN(sselfs as_string(sself((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pys__str__s(s__name__s __module__s__doc__snamesrequireds singletonssmultiples exclusives inclusives__init__s_encodessetsadds_decodes_markersdecodeds get_inlines set_inlines add_components_walksNoneswalksproperty_itemssFalses from_strings staticmethods__repr__s content_liness as_strings__str__(((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pys Component>s2 n         *  sEventcBstZdZdfZddddddd d d d d ddddddddfZddfZdddddddddddd f ZRS(!NsVEVENTsUIDsCLASSsCREATEDs DESCRIPTIONsDTSTARTsGEOsLAST-MODsLOCATIONs ORGANIZERsPRIORITYsDTSTAMPsSEQUENCEsSTATUSsSUMMARYsTRANSPsURLsRECURIDsDTENDsDURATIONsATTACHsATTENDEEs CATEGORIESsCOMMENTsCONTACTsEXDATEsEXRULEsRSTATUSsRELATEDs RESOURCESsRDATEsRRULE(s__name__s __module__snamesrequireds singletonss exclusivesmultiple(((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pysEvents  ? sTodocBstZdZdfZddddddd d d d d dddddddddfZddfZddddddddddd d!f ZRS("NsVTODOsUIDsCLASSs COMPLETEDsCREATEDs DESCRIPTIONsDTSTAMPsDTSTARTsGEOsLAST-MODsLOCATIONs ORGANIZERsPERCENTsPRIORITYsRECURIDsSEQUENCEsSTATUSsSUMMARYsURLsDUEsDURATIONsATTACHsATTENDEEs CATEGORIESsCOMMENTsCONTACTsEXDATEsEXRULEsRSTATUSsRELATEDs RESOURCESsRDATEsRRULE(s__name__s __module__snamesrequireds singletonss exclusivesmultiple(((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pysTodos  B sJournalc BsktZdZdfZddddddd d d d d ddf Zdddddddddddf ZRS(NsVJOURNALsUIDsCLASSsCREATEDs DESCRIPTIONsDTSTARTsDTSTAMPsLAST-MODs ORGANIZERsRECURIDsSEQUENCEsSTATUSsSUMMARYsURLsATTACHsATTENDEEs CATEGORIESsCOMMENTsCONTACTsEXDATEsEXRULEsRELATEDsRDATEsRRULEsRSTATUS(s__name__s __module__snamesrequireds singletonssmultiple(((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pysJournals -sFreeBusycBsGtZdZdfZdddddddd fZd d d d fZRS(Ns VFREEBUSYsUIDsCONTACTsDTSTARTsDTENDsDURATIONsDTSTAMPs ORGANIZERsURLsATTENDEEsCOMMENTsFREEBUSYsRSTATUS(s__name__s __module__snamesrequireds singletonssmultiple(((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pysFreeBusys sTimezonecBsGtZdZddddddfZdd dfZd d d d fZRS(Ns VTIMEZONEsTZIDs STANDARDCs DAYLIGHTCsDTSTARTs TZOFFSETTOs TZOFFSETFROMsLAST-MODsTZURLsCOMMENTsRDATEsRRULEsTZNAME(s__name__s __module__snamesrequireds singletonssmultiple(((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pysTimezonessAlarmcBsJtZdZddfZdddddfZddffZddfZRS( NsVALARMsACTIONsTRIGGERsATTACHsDURATIONsREPEATs STANDARDCs DAYLIGHTC(s__name__s __module__snamesrequireds singletonss inclusivesmultiple(((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pysAlarms  sCalendarcBs8tZdZdZddfZddfZddfZRS(s This is the base object for an iCalendar file. Setting up a minimal calendar component looks like this >>> cal = Calendar() Som properties are required to be compliant >>> cal['prodid'] = '-//My calendar product//mxm.dk//' >>> cal['version'] = '2.0' We also need at least one subcomponent for a calendar to be compliant >>> from datetime import datetime >>> event = Event() >>> event['summary'] = 'Python meeting about calendaring' >>> event['uid'] = '42' >>> event.set('dtstart', datetime(2005,4,4,8,0,0)) >>> cal.add_component(event) >>> cal.subcomponents[0].as_string() 'BEGIN:VEVENT\r\nDTSTART:20050404T080000\r\nSUMMARY:Python meeting about calendaring\r\nUID:42\r\nEND:VEVENT\r\n' Write to disc >>> import tempfile, os >>> directory = tempfile.mkdtemp() >>> open(os.path.join(directory, 'test.ics'), 'wb').write(cal.as_string()) s VCALENDARsprodidsversionscalscalesmethod(s__name__s __module__s__doc__snamesrequireds singletonssmultiple(((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pysCalendars   (!s__doc__stypessListTypes TupleTypes SequenceTypessresicalendar.caselessdicts CaselessDictsicalendar.parsers Contentliness Contentlines Parameterssq_splitsq_joinsicalendar.props TypesFactorysvTextsComponentFactorysappends_[1]scatsINLINEs_markers ComponentsEventsTodosJournalsFreeBusysTimezonesAlarmsCalendars types_factoryscomponent_factory(s Parameterss types_factorysComponentFactorysFreeBusysq_splitsJournalscomponent_factorys Componentsres SequenceTypess_markersAlarms TypesFactorysTimezonesListTypesq_joins TupleTypes Contentlinescats_[1]svTexts ContentlinessINLINEsCalendarsTodosEvents CaselessDict((s1build/bdist.darwin-8.0.1-x86/egg/icalendar/cal.pys? s*   <`   " PKg3u1Xicalendar/tools.pyfrom string import ascii_letters, digits import random """ This module contains non-essential tools for iCalendar. Pretty thin so far eh? """ class UIDGenerator: """ If you are too lazy to create real uid's. Notice, this doctest is disabled! Automatic semi-random uid >> g = UIDGenerator() >> uid = g.uid() >> uid.ical() '20050109T153222-7ekDDHKcw46QlwZK@example.com' You Should at least insert your own hostname to be more complient >> g = UIDGenerator() >> uid = g.uid('Example.ORG') >> uid.ical() '20050109T153549-NbUItOPDjQj8Ux6q@Example.ORG' You can also insert a path or similar >> g = UIDGenerator() >> uid = g.uid('Example.ORG', '/path/to/content') >> uid.ical() '20050109T153415-/path/to/content@Example.ORG' """ chars = list(ascii_letters + digits) def rnd_string(self, length=16): "Generates a string with random characters of length" return ''.join([random.choice(self.chars) for i in range(length)]) def uid(self, host_name='example.com', unique=''): """ Generates a unique id consisting of: datetime-uniquevalue@host. Like: 20050105T225746Z-HKtJMqUgdO0jDUwm@example.com """ from PropertyValues import vText, vDatetime unique = unique or self.rnd_string() return vText('%s-%s@%s' % (vDatetime.today().ical(), unique, host_name)) if __name__ == "__main__": import os.path, doctest, tools # import and test this file doctest.testmod(tools) PKZw5)EEicalendar/parser.py# -*- coding: latin-1 -*- """ This module parses and generates contentlines as defined in RFC 2445 (iCalendar), but will probably work for other MIME types with similar syntax. Eg. RFC 2426 (vCard) It is stupid in the sense that it treats the content purely as strings. No type conversion is attempted. Copyright, 2005: Max M License: GPL (Just contact med if and why you would like it changed) """ # from python from types import TupleType, ListType SequenceTypes = [TupleType, ListType] import re # from this package from icalendar.caselessdict import CaselessDict ################################################################# # Property parameter stuff def paramVal(val): "Returns a parameter value" if type(val) in SequenceTypes: return q_join(val) return dQuote(val) # Could be improved NAME = re.compile('[\w-]+') UNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F",:;]') QUNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F"]') FOLD = re.compile('([\r]?\n)+[ \t]{1}') def validate_token(name): match = NAME.findall(name) if len(match) == 1 and name == match[0]: return raise ValueError, name def validate_param_value(value, quoted=True): validator = UNSAFE_CHAR if quoted: validator = QUNSAFE_CHAR if validator.findall(value): raise ValueError, value QUOTABLE = re.compile('[,;:].') def dQuote(val): """ Parameter values containing [,;:] must be double quoted >>> dQuote('Max') 'Max' >>> dQuote('Rasmussen, Max') '"Rasmussen, Max"' >>> dQuote('name:value') '"name:value"' """ if QUOTABLE.search(val): return '"%s"' % val return val # parsing helper def q_split(st, sep=','): """ Splits a string on char, taking double (q)uotes into considderation >>> q_split('Max,Moller,"Rasmussen, Max"') ['Max', 'Moller', '"Rasmussen, Max"'] """ result = [] cursor = 0 length = len(st) inquote = 0 for i in range(length): ch = st[i] if ch == '"': inquote = not inquote if not inquote and ch == sep: result.append(st[cursor:i]) cursor = i + 1 if i + 1 == length: result.append(st[cursor:]) return result def q_join(lst, sep=','): """ Joins a list on sep, quoting strings with QUOTABLE chars >>> s = ['Max', 'Moller', 'Rasmussen, Max'] >>> q_join(s) 'Max,Moller,"Rasmussen, Max"' """ return sep.join([dQuote(itm) for itm in lst]) class Parameters(CaselessDict): """ Parser and generator of Property parameter strings. It knows nothing of datatypes. It's main concern is textual structure. Simple parameter:value pair >>> p = Parameters(parameter1='Value1') >>> str(p) 'PARAMETER1=Value1' keys are converted to upper >>> p.keys() ['PARAMETER1'] Parameters are case insensitive >>> p['parameter1'] 'Value1' >>> p['PARAMETER1'] 'Value1' Parameter with list of values must be seperated by comma >>> p = Parameters({'parameter1':['Value1', 'Value2']}) >>> str(p) 'PARAMETER1=Value1,Value2' Multiple parameters must be seperated by a semicolon >>> p = Parameters({'RSVP':'TRUE', 'ROLE':'REQ-PARTICIPANT'}) >>> str(p) 'ROLE=REQ-PARTICIPANT;RSVP=TRUE' Parameter values containing ',;:' must be double quoted >>> p = Parameters({'ALTREP':'http://www.wiz.org'}) >>> str(p) 'ALTREP="http://www.wiz.org"' list items must be quoted seperately >>> p = Parameters({'MEMBER':['MAILTO:projectA@host.com', 'MAILTO:projectB@host.com', ]}) >>> str(p) 'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"' Now the whole sheebang >>> p = Parameters({'parameter1':'Value1', 'parameter2':['Value2', 'Value3'],\ 'ALTREP':['http://www.wiz.org', 'value4']}) >>> str(p) 'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3' We can also parse parameter strings >>> Parameters.from_string('PARAMETER1=Value 1;param2=Value 2') Parameters({'PARAMETER1': 'Value 1', 'PARAM2': 'Value 2'}) Including empty strings >>> Parameters.from_string('param=') Parameters({'PARAM': ''}) We can also parse parameter strings >>> Parameters.from_string('MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"') Parameters({'MEMBER': ['MAILTO:projectA@host.com', 'MAILTO:projectB@host.com']}) We can also parse parameter strings >>> Parameters.from_string('ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3') Parameters({'PARAMETER1': 'Value1', 'ALTREP': ['http://www.wiz.org', 'value4'], 'PARAMETER2': ['Value2', 'Value3']}) """ def params(self): """ in rfc2445 keys are called parameters, so this is to be consitent with the naming conventions """ return self.keys() ### Later, when I get more time... need to finish this off now. The last majot thing missing. ### def _encode(self, name, value, cond=1): ### # internal, for conditional convertion of values. ### if cond: ### klass = types_factory.for_property(name) ### return klass(value) ### return value ### ### def add(self, name, value, encode=0): ### "Add a parameter value and optionally encode it." ### if encode: ### value = self._encode(name, value, encode) ### self[name] = value ### ### def decoded(self, name): ### "returns a decoded value, or list of same" def __repr__(self): return 'Parameters(' + dict.__repr__(self) + ')' def __str__(self): result = [] items = self.items() items.sort() # To make doctests work for key, value in items: value = paramVal(value) result.append('%s=%s' % (key.upper(), value)) return ';'.join(result) def from_string(st, strict=False): "Parses the parameter format from ical text format" try: # parse into strings result = Parameters() for param in q_split(st, ';'): key, val = q_split(param, '=') validate_token(key) param_values = [v for v in q_split(val, ',')] # Property parameter values that are not in quoted # strings are case insensitive. vals = [] for v in param_values: if v.startswith('"') and v.endswith('"'): v = v.strip('"') validate_param_value(v, quoted=True) vals.append(v) else: validate_param_value(v, quoted=False) if strict: vals.append(v.upper()) else: vals.append(v) if not vals: result[key] = val else: if len(vals) == 1: result[key] = vals[0] else: result[key] = vals return result except: raise ValueError, 'Not a valid parameter string' from_string = staticmethod(from_string) ######################################### # parsing and generation of content lines class Contentline(str): """ A content line is basically a string that can be folded and parsed into parts. >>> c = Contentline('Si meliora dies, ut vina, poemata reddit') >>> str(c) 'Si meliora dies, ut vina, poemata reddit' A long line gets folded >>> c = Contentline(''.join(['123456789 ']*10)) >>> str(c) '123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234\\r\\n 56789 123456789 123456789 ' A folded line gets unfolded >>> c = Contentline.from_string(str(c)) >>> c '123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 ' We do not fold within a UTF-8 character: >>> c = Contentline('This line has a UTF-8 character where it should be folded. Make sure it g\xc3\xabts folded before that character.') >>> '\xc3\xab' in str(c) True Don't fail if we fold a line that is exactly X times 74 characters long: >>> c = str(Contentline(''.join(['x']*148))) It can parse itself into parts. Which is a tuple of (name, params, vals) >>> c = Contentline('dtstart:20050101T120000') >>> c.parts() ('dtstart', Parameters({}), '20050101T120000') >>> c = Contentline('dtstart;value=datetime:20050101T120000') >>> c.parts() ('dtstart', Parameters({'VALUE': 'datetime'}), '20050101T120000') >>> c = Contentline('ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com') >>> c.parts() ('ATTENDEE', Parameters({'ROLE': 'REQ-PARTICIPANT', 'CN': 'Max Rasmussen'}), 'MAILTO:maxm@example.com') >>> str(c) 'ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com' and back again >>> parts = ('ATTENDEE', Parameters({'ROLE': 'REQ-PARTICIPANT', 'CN': 'Max Rasmussen'}), 'MAILTO:maxm@example.com') >>> Contentline.from_parts(parts) 'ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com' and again >>> parts = ('ATTENDEE', Parameters(), 'MAILTO:maxm@example.com') >>> Contentline.from_parts(parts) 'ATTENDEE:MAILTO:maxm@example.com' A value can also be any of the types defined in PropertyValues >>> from icalendar.prop import vText >>> parts = ('ATTENDEE', Parameters(), vText('MAILTO:test@example.com')) >>> Contentline.from_parts(parts) 'ATTENDEE:MAILTO:test@example.com' A value can also be unicode >>> from icalendar.prop import vText >>> parts = ('SUMMARY', Parameters(), vText(u'INternational char ')) >>> Contentline.from_parts(parts) 'SUMMARY:INternational char \\xc3\\xa6 \\xc3\\xb8 \\xc3\\xa5' Traversing could look like this. >>> name, params, vals = c.parts() >>> name 'ATTENDEE' >>> vals 'MAILTO:maxm@example.com' >>> for key, val in params.items(): ... (key, val) ('ROLE', 'REQ-PARTICIPANT') ('CN', 'Max Rasmussen') And the traditional failure >>> c = Contentline('ATTENDEE;maxm@example.com') >>> c.parts() Traceback (most recent call last): ... ValueError: Content line could not be parsed into parts Another failure: >>> c = Contentline(':maxm@example.com') >>> c.parts() Traceback (most recent call last): ... ValueError: Content line could not be parsed into parts >>> c = Contentline('key;param=:value') >>> c.parts() ('key', Parameters({'PARAM': ''}), 'value') >>> c = Contentline('key;param="pvalue":value') >>> c.parts() ('key', Parameters({'PARAM': 'pvalue'}), 'value') Should bomb on missing param: >>> c = Contentline.from_string("k;:no param") >>> c.parts() Traceback (most recent call last): ... ValueError: Content line could not be parsed into parts >>> c = Contentline('key;param=pvalue:value', strict=False) >>> c.parts() ('key', Parameters({'PARAM': 'pvalue'}), 'value') If strict is set to True, uppercase param values that are not double-quoted, this is because the spec says non-quoted params are case-insensitive. >>> c = Contentline('key;param=pvalue:value', strict=True) >>> c.parts() ('key', Parameters({'PARAM': 'PVALUE'}), 'value') >>> c = Contentline('key;param="pValue":value', strict=True) >>> c.parts() ('key', Parameters({'PARAM': 'pValue'}), 'value') """ def __new__(cls, st, strict=False): self = str.__new__(cls, st) setattr(self, 'strict', strict) return self def from_parts(parts): "Turns a tuple of parts into a content line" (name, params, values) = [str(p) for p in parts] try: if params: return Contentline('%s;%s:%s' % (name, params, values)) return Contentline('%s:%s' % (name, values)) except: raise ValueError( 'Property: %s Wrong values "%s" or "%s"' % (repr(name), repr(params), repr(values))) from_parts = staticmethod(from_parts) def parts(self): """ Splits the content line up into (name, parameters, values) parts """ try: name_split = None value_split = None inquotes = 0 for i in range(len(self)): ch = self[i] if not inquotes: if ch in ':;' and not name_split: name_split = i if ch == ':' and not value_split: value_split = i if ch == '"': inquotes = not inquotes name = self[:name_split] if not name: raise ValueError, 'Key name is required' validate_token(name) if name_split+1 == value_split: raise ValueError, 'Invalid content line' params = Parameters.from_string(self[name_split+1:value_split], strict=self.strict) values = self[value_split+1:] return (name, params, values) except: raise ValueError, 'Content line could not be parsed into parts' def from_string(st, strict=False): "Unfolds the content lines in an iCalendar into long content lines" try: # a fold is carriage return followed by either a space or a tab return Contentline(FOLD.sub('', st), strict=strict) except: raise ValueError, 'Expected StringType with content line' from_string = staticmethod(from_string) def __str__(self): "Long content lines are folded so they are less than 75 characters wide" l_line = len(self) new_lines = [] start = 0 end = 74 while True: if end >= l_line: end = l_line else: # Check that we don't fold in the middle of a UTF-8 character: # http://lists.osafoundation.org/pipermail/ietf-calsify/2006-August/001126.html while True: char_value = ord(self[end]) if char_value < 128 or char_value >= 192: # This is not in the middle of a UTF-8 character, so we # can fold here: break else: end -= 1 new_lines.append(self[start:end]) if end == l_line: # Done break start = end end = start + 74 return '\r\n '.join(new_lines) class Contentlines(list): """ I assume that iCalendar files generally are a few kilobytes in size. Then this should be efficient. for Huge files, an iterator should probably be used instead. >>> c = Contentlines([Contentline('BEGIN:VEVENT\\r\\n')]) >>> str(c) 'BEGIN:VEVENT\\r\\n' Lets try appending it with a 100 charater wide string >>> c.append(Contentline(''.join(['123456789 ']*10)+'\\r\\n')) >>> str(c) 'BEGIN:VEVENT\\r\\n\\r\\n123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234\\r\\n 56789 123456789 123456789 \\r\\n' Notice that there is an extra empty string in the end of the content lines. That is so they can be easily joined with: '\r\n'.join(contentlines)). >>> Contentlines.from_string('A short line\\r\\n') ['A short line', ''] >>> Contentlines.from_string('A faked\\r\\n long line\\r\\n') ['A faked long line', ''] >>> Contentlines.from_string('A faked\\r\\n long line\\r\\nAnd another lin\\r\\n\\te that is folded\\r\\n') ['A faked long line', 'And another line that is folded', ''] """ def __str__(self): "Simply join self." return '\r\n'.join(map(str, self)) def from_string(st): "Parses a string into content lines" try: # a fold is carriage return followed by either a space or a tab unfolded = FOLD.sub('', st) lines = [Contentline(line) for line in unfolded.splitlines() if line] lines.append('') # we need a '\r\n' in the end of every content line return Contentlines(lines) except: raise ValueError, 'Expected StringType with content lines' from_string = staticmethod(from_string) # ran this: # sample = open('./samples/test.ics', 'rb').read() # binary file in windows! # lines = Contentlines.from_string(sample) # for line in lines[:-1]: # print line.parts() # got this: #('BEGIN', Parameters({}), 'VCALENDAR') #('METHOD', Parameters({}), 'Request') #('PRODID', Parameters({}), '-//My product//mxm.dk/') #('VERSION', Parameters({}), '2.0') #('BEGIN', Parameters({}), 'VEVENT') #('DESCRIPTION', Parameters({}), 'This is a very long description that ...') #('PARTICIPANT', Parameters({'CN': 'Max M'}), 'MAILTO:maxm@mxm.dk') #('DTEND', Parameters({}), '20050107T160000') #('DTSTART', Parameters({}), '20050107T120000') #('SUMMARY', Parameters({}), 'A second event') #('END', Parameters({}), 'VEVENT') #('BEGIN', Parameters({}), 'VEVENT') #('DTEND', Parameters({}), '20050108T235900') #('DTSTART', Parameters({}), '20050108T230000') #('SUMMARY', Parameters({}), 'A single event') #('UID', Parameters({}), '42') #('END', Parameters({}), 'VEVENT') #('END', Parameters({}), 'VCALENDAR') PKC8pl;4 icalendar/util.pyc; ,oCc@s3dklZlZdkZdfdYZdS((s ascii_letterssdigitsNs UIDGeneratorcBs9tZdZeeeZddZdddZRS(s If you are too lazy to create real uids. NOTE: this doctest is disabled (only two > instead of three) Automatic semi-random uid >> g = UIDGenerator() >> uid = g.uid() >> uid.ical() '20050109T153222-7ekDDHKcw46QlwZK@example.com' You should at least insert your own hostname to be more compliant >> g = UIDGenerator() >> uid = g.uid('Example.ORG') >> uid.ical() '20050109T153549-NbUItOPDjQj8Ux6q@Example.ORG' You can also insert a path or similar >> g = UIDGenerator() >> uid = g.uid('Example.ORG', '/path/to/content') >> uid.ical() '20050109T153415-/path/to/content@Example.ORG' icCsDdigi}t|D]}|ti|i q~SdS(s3Generates a string with random characters of lengthsN( sjoinsappends_[1]srangeslengthsisrandomschoicesselfschars(sselfslengthsis_[1]((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/util.pys rnd_string&ss example.comscCsMdkl}l}|p |i}|d|ii||fSdS(s Generates a unique id consisting of: datetime-uniquevalue@host. Like: 20050105T225746Z-HKtJMqUgdO0jDUwm@example.com (svTexts vDatetimes%s-%s@%sN( sPropertyValuessvTexts vDatetimesuniquesselfs rnd_stringstodaysicals host_name(sselfs host_namesuniques vDatetimesvText((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/util.pysuid*s( s__name__s __module__s__doc__slists ascii_letterssdigitsscharss rnd_stringsuid(((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/util.pys UIDGenerator s  (sstrings ascii_letterssdigitssrandoms UIDGenerator(sdigitss UIDGenerators ascii_letterssrandom((s2build/bdist.darwin-8.0.1-x86/egg/icalendar/util.pys?s PKC8:\99icalendar/interfaces.pyc; pCc@sydklZlZWn9ej o-dfdYZdfdYZnXeZdefdYZdefdYZd efd YZd efd YZ d efdYZ defdYZ defdYZ defdYZ defdYZdefdYZdefdYZdefdYZdefdYZdefd YZd!efd"YZd#efd$YZd%efd&YZd'efd(YZd)efd*YZd+efd,YZd-efd.YZd/efd0YZd1efd2YZd3efd4YZd5efd6YZd7efd8YZd9efd:YZ d;S(<(s Interfaces Attributes InterfacecBstZdZRS(sA dummy interface base class(s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys Interfaces s AttributecBstZdZdZRS(s A dummy attribute implementationcCs ||_dS(N(sdocsself(sselfsdoc((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys__init__ s(s__name__s __module__s__doc__s__init__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys Attributes s IComponentcBstZdZdZddZdZdZedZdZ e dZ dd Z d Z ed Zed Zed ZdZRS(s Component is the base object for calendar, Event and the other components defined in RFC 2445. A component is like a dictionary with extra methods and attributes. cCsdS(sSet a property. name - case insensitive name value - value of the property to set. This can be either a single item or a list. Some iCalendar properties are set INLINE; these properties have multiple values on one property line in the iCalendar representation. The list can be supplied as a comma separated string to __setitem__. If special iCalendar characters exist in an entry, such as the colon (:) and (,), that comma-separated entry needs to be quoted with double quotes. For example: 'foo, bar, "baz:hoi"' See also set_inline() for an easier way to deal with this case. N((snamesvalue((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys __setitem__sicCsdS(sASet list of INLINE values for property. Converts a list of values into valid iCalendar comma seperated string and sets value to that. name - case insensitive name of property values - list of values to set encode - if True, encode Python values as iCalendar types first. N((snamesvaluessencode((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys set_inline+s cCsdS(sAdd a property. Can be called multiple times to set a list. name - case insensitive name value - value of property to set or add to list for this property. N((snamesvalue((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysadd6scCsdS(s5Add a nested subcomponent to this component. N((s component((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys add_component=scCsdS(sPopulates the component recursively from a iCalendar string. Reads the iCalendar string and constructs components and subcomponents out of it. N((sstsmultiple((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys from_stringBscCsdS(szGet a property name - case insensitive name Returns an iCalendar property object such as vText. N((sname((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys __getitem__JscCsdS(sJGet a property as a python object. name - case insensitive name default - optional argument. If supplied, will use this if name cannot be found. If not supplied, decoded will raise a KeyError if name cannot be found. Returns python object (such as unicode string, datetime, etc). N((snamesdefault((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysdecodedRs cCsdS(sGet list of INLINE values from property. name - case insensitive name decode - decode to Python objects. Returns list of python objects. N((snamesdecode((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys get_inline]scCsdS(soRender the component in the RFC 2445 (iCalendar) format. Returns a string in RFC 2445 format. N((((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys as_stringfss[ A list of all subcomponents of this component, added using add_component()s6 Name of this component (VEVENT, etc) cCsdS(sRecursively traverses component and subcomponents. name - optional, if given, only return components with that name Returns sequence of components. N((sname((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pyswalktscCsdS(sReturn properties as (name, value) tuples. Returns all properties in this comopnent and subcomponents as name, value tuples. N((((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysproperty_items|s(s__name__s __module__s__doc__s __setitem__s set_inlinesadds add_componentsFalses from_strings __getitem__s_markersdecodeds get_inlines as_strings Attributes subcomponentssnamesNoneswalksproperty_items(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys IComponents          sIEventcBstZdZRS(s7A component which conforms to an iCalendar VEVENT. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIEvents sITodocBstZdZRS(s6A component which conforms to an iCalendar VTODO. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysITodos sIJournalcBstZdZRS(s9A component which conforms to an iCalendar VJOURNAL. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIJournals s IFreeBusycBstZdZRS(s:A component which conforms to an iCalendar VFREEBUSY. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys IFreeBusys s ITimezonecBstZdZRS(s:A component which conforms to an iCalendar VTIMEZONE. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys ITimezones sIAlarmcBstZdZRS(s7A component which conforms to an iCalendar VALARM. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIAlarms s ICalendarcBstZdZRS(s:A component which conforms to an iCalendar VCALENDAR. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys ICalendars sIPropertyValuecBs tZdZdZdZRS(sAn iCalendar property value. iCalendar properties have strongly typed values. This invariance should always be true: assert x == vDataType.from_ical(vDataType(x).ical()) cCsdS(sERender property as string, as defined in iCalendar RFC 2445. N((((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysicalscCsdS(sQParse property from iCalendar RFC 2445 text. Inverse of ical(). N((sical((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys from_icals(s__name__s __module__s__doc__sicals from_ical(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIPropertyValues  sIBinarycBstZdZRS(s/Binary property values are base 64 encoded (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIBinarys sIBooleancBstZdZRS(s;Boolean property. Also behaves like a python int. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIBooleans s ICalAddresscBstZdZRS(s8Email address. Also behaves like a python str. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys ICalAddresss s IDateTimecBstZdZRS(sRender and generates iCalendar datetime format. Important: if tzinfo is defined it renders itself as 'date with utc time' Meaning that it has a 'Z' appended, and is in absolute time. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys IDateTimes sIDatecBstZdZRS(s0Render and generates iCalendar date format. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIDates s IDurationcBstZdZRS(sARender and generates timedelta in iCalendar DURATION format. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys IDurations sIFloatcBstZdZRS(s[Render and generate floats in iCalendar format. Also behaves like a python float. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIFloats sIIntcBstZdZRS(sWRender and generate ints in iCalendar format. Also behaves like a python int. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIInts sIPeriodcBstZdZRS(s3A precise period of time (datetime, datetime). (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIPeriods sIWeekDaycBstZdZRS(s.Render and generate weekday abbreviation. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIWeekDays s IFrequencycBstZdZRS(sFrequency. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys IFrequencys sIRecurcBstZdZRS(sqRender and generate data based on recurrent event representation. This acts like a caseless dictionary. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIRecurs sITextcBstZdZRS(sUnicode text. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysITexts sITimecBstZdZRS(s Time. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysITimes sIUricBstZdZRS(sURI (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIUris sIGeocBstZdZRS(sGeographical location. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIGeos s IUTCOffsetcBstZdZRS(sOffset from UTC. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys IUTCOffsets sIInlinecBstZdZRS(sInline list. (s__name__s __module__s__doc__(((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pysIInlines N(!szope.interfaces Interfaces Attributes ImportErrorsobjects_markers IComponentsIEventsITodosIJournals IFreeBusys ITimezonesIAlarms ICalendarsIPropertyValuesIBinarysIBooleans ICalAddresss IDateTimesIDates IDurationsIFloatsIIntsIPeriodsIWeekDays IFrequencysIRecursITextsITimesIUrisIGeos IUTCOffsetsIInline(s IDateTimesIJournals IDurationsIRecursITextsIFloatsIWeekDays ICalAddresssIBooleansIAlarmsIDates IFrequencysIUrisIBinarysIIntsIInlines_markersITimes IComponentsITodos InterfacesIEventsIPropertyValues ICalendarsIPeriods IUTCOffsets Attributes ITimezonesIGeos IFreeBusy((s8build/bdist.darwin-8.0.1-x86/egg/icalendar/interfaces.pys?s@ uPK`v5ki i icalendar/caselessdict.py# -*- coding: latin-1 -*- class CaselessDict(dict): """ A dictionary that isn't case sensitive, and only use string as keys. >>> ncd = CaselessDict(key1='val1', key2='val2') >>> ncd CaselessDict({'KEY2': 'val2', 'KEY1': 'val1'}) >>> ncd['key1'] 'val1' >>> ncd['KEY1'] 'val1' >>> ncd['KEY3'] = 'val3' >>> ncd['key3'] 'val3' >>> ncd.setdefault('key3', 'FOUND') 'val3' >>> ncd.setdefault('key4', 'NOT FOUND') 'NOT FOUND' >>> ncd['key4'] 'NOT FOUND' >>> ncd.get('key1') 'val1' >>> ncd.get('key3', 'NOT FOUND') 'val3' >>> ncd.get('key4', 'NOT FOUND') 'NOT FOUND' >>> 'key4' in ncd True >>> del ncd['key4'] >>> ncd.has_key('key4') False >>> ncd.update({'key5':'val5', 'KEY6':'val6', 'KEY5':'val7'}) >>> ncd['key6'] 'val6' >>> keys = ncd.keys() >>> keys.sort() >>> keys ['KEY1', 'KEY2', 'KEY3', 'KEY5', 'KEY6'] """ def __init__(self, *args, **kwargs): "Set keys to upper for initial dict" dict.__init__(self, *args, **kwargs) for k,v in self.items(): k_upper = k.upper() if k != k_upper: dict.__delitem__(self, k) self[k_upper] = v def __getitem__(self, key): return dict.__getitem__(self, key.upper()) def __setitem__(self, key, value): dict.__setitem__(self, key.upper(), value) def __delitem__(self, key): dict.__delitem__(self, key.upper()) def __contains__(self, item): return dict.__contains__(self, item.upper()) def get(self, key, default=None): return dict.get(self, key.upper(), default) def setdefault(self, key, value=None): return dict.setdefault(self, key.upper(), value) def pop(self, key, default=None): return dict.pop(self, key.upper(), default) def popitem(self): return dict.popitem(self) def has_key(self, key): return dict.has_key(self, key.upper()) def update(self, indict): """ Multiple keys where key1.upper() == key2.upper() will be lost. """ for entry in indict: self[entry] = indict[entry] def copy(self): return CaselessDict(dict.copy(self)) def clear(self): dict.clear(self) def __repr__(self): return 'CaselessDict(' + dict.__repr__(self) + ')' PKC8L /icalendar/__init__.pyc; ,oCc@sdklZlZlZlZdklZlZlZlZdk l Z l Z l Z l Z lZlZlZlZlZlZlZlZlZlZlZlZlZlZlZdk lZlZlZdk l!Z!l"Z"l#Z#dS((sCalendarsEventsTodosJournal(sFreeBusysTimezonesAlarmsComponentFactory(svBinarysvBooleans vCalAddresss vDatetimesvDates vDDDTypess vDurationsvFloatsvIntsvPeriodsvWeekdays vFrequencysvRecursvTextsvTimesvUrisvGeos vUTCOffsets TypesFactory(s FixedOffsetsUTCs LocalTimezone(s Parameterssq_splitsq_joinN($s icalendar.calsCalendarsEventsTodosJournalsFreeBusysTimezonesAlarmsComponentFactorysicalendar.propsvBinarysvBooleans vCalAddresss vDatetimesvDates vDDDTypess vDurationsvFloatsvIntsvPeriodsvWeekdays vFrequencysvRecursvTextsvTimesvUrisvGeos vUTCOffsets TypesFactorys FixedOffsetsUTCs LocalTimezonesicalendar.parsers Parameterssq_splitsq_join(!svTimes ParameterssvDates vDurationsComponentFactorysFreeBusysq_splitsvPeriods vDatetimesJournalsvGeosvFloatsvRecurs LocalTimezonesvIntsUTCsvBooleans vFrequencys vCalAddresssAlarms vUTCOffsets TypesFactorys FixedOffsetsq_joinsvWeekdaysvBinarysvUrisvTexts vDDDTypessTimezonesCalendarsTodosEvent((s6build/bdist.darwin-8.0.1-x86/egg/icalendar/__init__.pys?syPKC82EGG-INFO/zip-safe PKC8VllEGG-INFO/SOURCES.txtREADME.txt setup.py src/icalendar/__init__.py src/icalendar/cal.py src/icalendar/caselessdict.py src/icalendar/interfaces.py src/icalendar/parser.py src/icalendar/prop.py src/icalendar/tools.py src/icalendar/util.py src/icalendar.egg-info/PKG-INFO src/icalendar.egg-info/SOURCES.txt src/icalendar.egg-info/dependency_links.txt src/icalendar.egg-info/top_level.txt PKC82EGG-INFO/dependency_links.txt PKC8X ]''EGG-INFO/PKG-INFOMetadata-Version: 1.0 Name: icalendar Version: 1.2 Summary: iCalendar parser/generator Home-page: http://codespeak.net/icalendar/ Author: MaxM Author-email: maxm@mxm.dk License: GPL2.1 Description: iCalendar is a parser/generator of iCalendar files (RFC 2445) for use with Python. Keywords: calendar icalendar Platform: All Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: OS Independent PKC8w EGG-INFO/top_level.txticalendar PKlv5$DDicalendar/cal.pyPKC8GGG5Dicalendar/parser.pycPKg3Jrqq"icalendar/__init__.pyPKg3Ǝicalendar/util.pyPKC8ܑ22icalendar/caselessdict.pycPKC8X  icalendar/tools.pycPKev5\Ҳicalendar/prop.pyPKE?h3 /55Oicalendar/interfaces.pyPKC8ār!jicalendar/prop.pycPKC8JNNTJicalendar/cal.pycPKg3u1Xicalendar/tools.pyPKZw5)EE8icalendar/parser.pyPKC8pl;4 )icalendar/util.pycPKC8:\99dicalendar/interfaces.pycPK`v5ki i (icalendar/caselessdict.pyPKC8L /92icalendar/__init__.pycPKC829EGG-INFO/zip-safePKC8VllD9EGG-INFO/SOURCES.txtPKC82:EGG-INFO/dependency_links.txtPKC8X ]'';EGG-INFO/PKG-INFOPKC8w t=EGG-INFO/top_level.txtPKo=