Back to index

plone3  3.1.7
prop.py
Go to the documentation of this file.
00001 # -*- coding: latin-1 -*-
00002 
00003 """
00004 
00005 This module contains the parser/generators (or coders/encoders if you prefer)
00006 for the classes/datatypes that are used in Icalendar:
00007 
00008 ###########################################################################
00009 # This module defines these property value data types and property parameters
00010 
00011 4.2 Defined property parameters are:
00012 
00013      ALTREP, CN, CUTYPE, DELEGATED-FROM, DELEGATED-TO, DIR, ENCODING, FMTTYPE,
00014      FBTYPE, LANGUAGE, MEMBER, PARTSTAT, RANGE, RELATED, RELTYPE, ROLE, RSVP,
00015      SENT-BY, TZID, VALUE
00016 
00017 4.3 Defined value data types are:
00018 
00019     BINARY, BOOLEAN, CAL-ADDRESS, DATE, DATE-TIME, DURATION, FLOAT, INTEGER,
00020     PERIOD, RECUR, TEXT, TIME, URI, UTC-OFFSET
00021 
00022 ###########################################################################
00023 
00024 
00025 iCalendar properties has values. The values are strongly typed. This module
00026 defines these types, calling val.ical() on them, Will render them as defined in
00027 rfc2445.
00028 
00029 If you pass any of these classes a Python primitive, you will have an object
00030 that can render itself as iCalendar formatted date.
00031 
00032 Property Value Data Types starts with a 'v'. they all have an ical() and
00033 from_ical() method. The ical() method generates a text string in the iCalendar
00034 format. The from_ical() method can parse this format and return a primitive
00035 Python datatype. So it should allways be true that:
00036 
00037     x == vDataType.from_ical(VDataType(x).ical())
00038 
00039 These types are mainly used for parsing and file generation. But you can set
00040 them directly.
00041 
00042 """
00043 
00044 # from python >= 2.3
00045 from datetime import datetime, timedelta, time, date, tzinfo
00046 from types import IntType, StringType, UnicodeType, TupleType, ListType
00047 SequenceTypes = [TupleType, ListType]
00048 import re
00049 import time as _time
00050 
00051 # from this package
00052 from icalendar.caselessdict import CaselessDict
00053 from icalendar.parser import Parameters
00054 
00055 DATE_PART = r'(\d+)D'
00056 TIME_PART = r'T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?'
00057 DATETIME_PART = '(?:%s)?(?:%s)?' % (DATE_PART, TIME_PART)
00058 WEEKS_PART = r'(\d+)W'
00059 DURATION_REGEX = re.compile(r'([-+]?)P(?:%s|%s)$'
00060                             % (WEEKS_PART, DATETIME_PART))
00061 WEEKDAY_RULE = re.compile('(?P<signal>[+-]?)(?P<relative>[\d]?)'
00062                           '(?P<weekday>[\w]{2})$')
00063 
00064 class vBinary:
00065     """
00066     Binary property values are base 64 encoded
00067     >>> b = vBinary('This is gibberish')
00068     >>> b.ical()
00069     'VGhpcyBpcyBnaWJiZXJpc2g='
00070     >>> b = vBinary.from_ical('VGhpcyBpcyBnaWJiZXJpc2g=')
00071     >>> b
00072     'This is gibberish'
00073 
00074     The roundtrip test
00075     >>> x = 'Binary data    \x13 \x56'
00076     >>> vBinary(x).ical()
00077     'QmluYXJ5IGRhdGEg5iD4IOUgEyBW'
00078     >>> vBinary.from_ical('QmluYXJ5IGRhdGEg5iD4IOUgEyBW')
00079     'Binary data \\xe6 \\xf8 \\xe5 \\x13 V'
00080 
00081     >>> b = vBinary('txt')
00082     >>> b.params
00083     Parameters({'VALUE': 'BINARY', 'ENCODING': 'BASE64'})
00084     """
00085 
00086     def __init__(self, obj):
00087         self.obj = obj
00088         self.params = Parameters(encoding='BASE64', value="BINARY")
00089 
00090     def __repr__(self):
00091         return "vBinary(%s)" % str.__repr__(self.obj)
00092 
00093     def ical(self):
00094         return self.obj.encode('base-64')[:-1]
00095 
00096     def from_ical(ical):
00097         "Parses the data format from ical text format"
00098         try:
00099             return ical.decode('base-64')
00100         except:
00101             raise ValueError, 'Not valid base 64 encoding.'
00102     from_ical = staticmethod(from_ical)
00103 
00104     def __str__(self):
00105         return self.ical()
00106 
00107 
00108 
00109 class vBoolean(int):
00110     """
00111     Returns specific string according to state
00112     >>> bin = vBoolean(True)
00113     >>> bin.ical()
00114     'TRUE'
00115     >>> bin = vBoolean(0)
00116     >>> bin.ical()
00117     'FALSE'
00118 
00119     The roundtrip test
00120     >>> x = True
00121     >>> x == vBoolean.from_ical(vBoolean(x).ical())
00122     True
00123     >>> vBoolean.from_ical('true')
00124     True
00125     """
00126 
00127     def __init__(self, *args, **kwargs):
00128         int.__init__(self, *args, **kwargs)
00129         self.params = Parameters()
00130 
00131     def ical(self):
00132         if self:
00133             return 'TRUE'
00134         return 'FALSE'
00135 
00136     bool_map = CaselessDict(true=True, false=False)
00137 
00138     def from_ical(ical):
00139         "Parses the data format from ical text format"
00140         try:
00141             return vBoolean.bool_map[ical]
00142         except:
00143             raise ValueError, "Expected 'TRUE' or 'FALSE'. Got %s" % ical
00144     from_ical = staticmethod(from_ical)
00145 
00146     def __str__(self):
00147         return self.ical()
00148 
00149 
00150 
00151 class vCalAddress(str):
00152     """
00153     This just returns an unquoted string
00154     >>> a = vCalAddress('MAILTO:maxm@mxm.dk')
00155     >>> a.params['cn'] = 'Max M'
00156     >>> a.ical()
00157     'MAILTO:maxm@mxm.dk'
00158     >>> str(a)
00159     'MAILTO:maxm@mxm.dk'
00160     >>> a.params
00161     Parameters({'CN': 'Max M'})
00162     >>> vCalAddress.from_ical('MAILTO:maxm@mxm.dk')
00163     'MAILTO:maxm@mxm.dk'
00164     """
00165 
00166     def __init__(self, *args, **kwargs):
00167         str.__init__(self, *args, **kwargs)
00168         self.params = Parameters()
00169 
00170     def __repr__(self):
00171         return u"vCalAddress(%s)" % str.__repr__(self)
00172 
00173     def ical(self):
00174         return str(self)
00175 
00176     def from_ical(ical):
00177         "Parses the data format from ical text format"
00178         try:
00179             return str(ical)
00180         except:
00181             raise ValueError, 'Expected vCalAddress, got: %s' % ical
00182     from_ical = staticmethod(from_ical)
00183 
00184     def __str__(self):
00185         return str.__str__(self)
00186 
00187 ####################################################
00188 # handy tzinfo classes you can use.
00189 
00190 ZERO = timedelta(0)
00191 HOUR = timedelta(hours=1)
00192 STDOFFSET = timedelta(seconds = -_time.timezone)
00193 if _time.daylight:
00194     DSTOFFSET = timedelta(seconds = -_time.altzone)
00195 else:
00196     DSTOFFSET = STDOFFSET
00197 DSTDIFF = DSTOFFSET - STDOFFSET
00198 
00199 
00200 class FixedOffset(tzinfo):
00201     """Fixed offset in minutes east from UTC."""
00202 
00203     def __init__(self, offset, name):
00204         self.__offset = timedelta(minutes = offset)
00205         self.__name = name
00206 
00207     def utcoffset(self, dt):
00208         return self.__offset
00209 
00210     def tzname(self, dt):
00211         return self.__name
00212 
00213     def dst(self, dt):
00214         return ZERO
00215 
00216 
00217 class UTC(tzinfo):
00218     """UTC tzinfo subclass"""
00219 
00220     def utcoffset(self, dt):
00221         return ZERO
00222 
00223     def tzname(self, dt):
00224         return "UTC"
00225 
00226     def dst(self, dt):
00227         return ZERO
00228 UTC = UTC()
00229 
00230 class LocalTimezone(tzinfo):
00231     """
00232     Timezone of the machine where the code is running
00233     """
00234 
00235     def utcoffset(self, dt):
00236         if self._isdst(dt):
00237             return DSTOFFSET
00238         else:
00239             return STDOFFSET
00240 
00241     def dst(self, dt):
00242         if self._isdst(dt):
00243             return DSTDIFF
00244         else:
00245             return ZERO
00246 
00247     def tzname(self, dt):
00248         return _time.tzname[self._isdst(dt)]
00249 
00250     def _isdst(self, dt):
00251         tt = (dt.year, dt.month, dt.day,
00252               dt.hour, dt.minute, dt.second,
00253               dt.weekday(), 0, -1)
00254         stamp = _time.mktime(tt)
00255         tt = _time.localtime(stamp)
00256         return tt.tm_isdst > 0
00257 
00258 ####################################################
00259 
00260 
00261 
00262 class vDatetime:
00263     """
00264     Render and generates iCalendar datetime format.
00265 
00266     Important: if tzinfo is defined it renders itself as "date with utc time"
00267     Meaning that it has a 'Z' appended, and is in absolute time.
00268 
00269     >>> d = datetime(2001, 1,1, 12, 30, 0)
00270 
00271     >>> dt = vDatetime(d)
00272     >>> dt.ical()
00273     '20010101T123000'
00274 
00275     >>> vDatetime.from_ical('20000101T120000')
00276     datetime.datetime(2000, 1, 1, 12, 0)
00277 
00278     >>> dutc = datetime(2001, 1,1, 12, 30, 0, tzinfo=UTC)
00279     >>> vDatetime(dutc).ical()
00280     '20010101T123000Z'
00281 
00282     >>> vDatetime.from_ical('20010101T000000')
00283     datetime.datetime(2001, 1, 1, 0, 0)
00284 
00285     >>> vDatetime.from_ical('20010101T000000A')
00286     Traceback (most recent call last):
00287       ...
00288     ValueError: Wrong datetime format: 20010101T000000A
00289 
00290     >>> utc = vDatetime.from_ical('20010101T000000Z')
00291     >>> vDatetime(utc).ical()
00292     '20010101T000000Z'
00293     """
00294 
00295     def __init__(self, dt):
00296         self.dt = dt
00297         self.params = Parameters()
00298 
00299     def ical(self):
00300         if self.dt.tzinfo:
00301             offset = self.dt.tzinfo.utcoffset(datetime.now())
00302             utc_time = self.dt - self.dt.tzinfo.utcoffset(datetime.now())
00303             return utc_time.strftime("%Y%m%dT%H%M%SZ")
00304         return self.dt.strftime("%Y%m%dT%H%M%S")
00305 
00306     def from_ical(ical):
00307         "Parses the data format from ical text format"
00308         try:
00309             timetuple = map(int, ((
00310                 ical[:4],       # year
00311                 ical[4:6],      # month
00312                 ical[6:8],      # day
00313                 ical[9:11],     # hour
00314                 ical[11:13],    # minute
00315                 ical[13:15],    # second
00316                 )))
00317             if not ical[15:]:
00318                 return datetime(*timetuple)
00319             elif ical[15:16] == 'Z':
00320                 timetuple += [0, UTC]
00321                 return datetime(*timetuple)
00322             else:
00323                 raise ValueError, ical
00324         except:
00325             raise ValueError, 'Wrong datetime format: %s' % ical
00326     from_ical = staticmethod(from_ical)
00327 
00328     def __str__(self):
00329         return self.ical()
00330 
00331 
00332 
00333 class vDate:
00334     """
00335     Render and generates iCalendar date format.
00336     >>> d = date(2001, 1,1)
00337     >>> vDate(d).ical()
00338     '20010101'
00339 
00340     >>> vDate.from_ical('20010102')
00341     datetime.date(2001, 1, 2)
00342 
00343     >>> vDate('d').ical()
00344     Traceback (most recent call last):
00345         ...
00346     ValueError: Value MUST be a date instance
00347     """
00348 
00349     def __init__(self, dt):
00350         if not isinstance(dt, date):
00351             raise ValueError('Value MUST be a date instance')
00352         self.dt = dt
00353         self.params = Parameters()
00354 
00355     def ical(self):
00356         return self.dt.strftime("%Y%m%d")
00357 
00358     def from_ical(ical):
00359         "Parses the data format from ical text format"
00360         try:
00361             timetuple = map(int, ((
00362                 ical[:4],     # year
00363                 ical[4:6],    # month
00364                 ical[6:8],    # day
00365                 )))
00366             return date(*timetuple)
00367         except:
00368             raise ValueError, 'Wrong date format %s' % ical
00369     from_ical = staticmethod(from_ical)
00370 
00371     def __str__(self):
00372         return self.ical()
00373 
00374 
00375 
00376 class vDuration:
00377     """
00378     Subclass of timedelta that renders itself in the iCalendar DURATION format.
00379 
00380     >>> vDuration(timedelta(11)).ical()
00381     'P11D'
00382     >>> vDuration(timedelta(-14)).ical()
00383     '-P14D'
00384     >>> vDuration(timedelta(1, 7384)).ical()
00385     'P1DT2H3M4S'
00386     >>> vDuration(timedelta(1, 7380)).ical()
00387     'P1DT2H3M'
00388     >>> vDuration(timedelta(1, 7200)).ical()
00389     'P1DT2H'
00390     >>> vDuration(timedelta(0, 7200)).ical()
00391     'PT2H'
00392     >>> vDuration(timedelta(0, 7384)).ical()
00393     'PT2H3M4S'
00394     >>> vDuration(timedelta(0, 184)).ical()
00395     'PT3M4S'
00396     >>> vDuration(timedelta(0, 22)).ical()
00397     'PT22S'
00398     >>> vDuration(timedelta(0, 3622)).ical()
00399     'PT1H0M22S'
00400 
00401     How does the parsing work?
00402     >>> vDuration.from_ical('PT1H0M22S')
00403     datetime.timedelta(0, 3622)
00404 
00405     >>> vDuration.from_ical('kox')
00406     Traceback (most recent call last):
00407         ...
00408     ValueError: Invalid iCalendar duration: kox
00409 
00410     >>> vDuration.from_ical('-P14D')
00411     datetime.timedelta(-14)
00412 
00413     >>> vDuration(11)
00414     Traceback (most recent call last):
00415         ...
00416     ValueError: Value MUST be a timedelta instance
00417     """
00418 
00419     def __init__(self, td):
00420         if not isinstance(td, timedelta):
00421             raise ValueError('Value MUST be a timedelta instance')
00422         self.td = td
00423         self.params = Parameters()
00424 
00425     def ical(self):
00426         sign = ""
00427         if self.td.days < 0:
00428             sign = "-"
00429         timepart = ""
00430         if self.td.seconds:
00431             timepart = "T"
00432             hours = self.td.seconds // 3600
00433             minutes = self.td.seconds % 3600 // 60
00434             seconds = self.td.seconds % 60
00435             if hours:
00436                 timepart += "%dH" % hours
00437             if minutes or (hours and seconds):
00438                 timepart += "%dM" % minutes
00439             if seconds:
00440                 timepart += "%dS" % seconds
00441         if self.td.days == 0 and timepart:
00442             return "%sP%s" % (sign, timepart)
00443         else:
00444             return "%sP%dD%s" % (sign, abs(self.td.days), timepart)
00445 
00446     def from_ical(ical):
00447         """
00448         Parses the data format from ical text format.
00449         """
00450         try:
00451             match = DURATION_REGEX.match(ical)
00452             sign, weeks, days, hours, minutes, seconds = match.groups()
00453             if weeks:
00454                 value = timedelta(weeks=int(weeks))
00455             else:
00456                 value = timedelta(days=int(days or 0),
00457                                   hours=int(hours or 0),
00458                                   minutes=int(minutes or 0),
00459                                   seconds=int(seconds or 0))
00460             if sign == '-':
00461                 value = -value
00462             return value
00463         except:
00464             raise ValueError('Invalid iCalendar duration: %s' % ical)
00465     from_ical = staticmethod(from_ical)
00466 
00467     def __str__(self):
00468         return self.ical()
00469 
00470 
00471 
00472 class vFloat(float):
00473     """
00474     Just a float.
00475     >>> f = vFloat(1.0)
00476     >>> f.ical()
00477     '1.0'
00478     >>> vFloat.from_ical('42')
00479     42.0
00480     >>> vFloat(42).ical()
00481     '42.0'
00482     """
00483 
00484     def __init__(self, *args, **kwargs):
00485         float.__init__(self, *args, **kwargs)
00486         self.params = Parameters()
00487 
00488     def ical(self):
00489         return str(self)
00490 
00491     def from_ical(ical):
00492         "Parses the data format from ical text format"
00493         try:
00494             return float(ical)
00495         except:
00496             raise ValueError, 'Expected float value, got: %s' % ical
00497     from_ical = staticmethod(from_ical)
00498 
00499 
00500 
00501 class vInt(int):
00502     """
00503     Just an int.
00504     >>> f = vInt(42)
00505     >>> f.ical()
00506     '42'
00507     >>> vInt.from_ical('13')
00508     13
00509     >>> vInt.from_ical('1s3')
00510     Traceback (most recent call last):
00511         ...
00512     ValueError: Expected int, got: 1s3
00513     """
00514 
00515     def __init__(self, *args, **kwargs):
00516         int.__init__(self, *args, **kwargs)
00517         self.params = Parameters()
00518 
00519     def ical(self):
00520         return str(self)
00521 
00522     def from_ical(ical):
00523         "Parses the data format from ical text format"
00524         try:
00525             return int(ical)
00526         except:
00527             raise ValueError, 'Expected int, got: %s' % ical
00528     from_ical = staticmethod(from_ical)
00529 
00530 
00531 
00532 class vDDDTypes:
00533     """
00534     A combined Datetime, Date or Duration parser/generator. Their format cannot
00535     be confused, and often values can be of either types. So this is practical.
00536 
00537     >>> d = vDDDTypes.from_ical('20010101T123000')
00538     >>> type(d)
00539     <type 'datetime.datetime'>
00540 
00541     >>> repr(vDDDTypes.from_ical('20010101T123000Z'))[:65]
00542     'datetime.datetime(2001, 1, 1, 12, 30, tzinfo=<icalendar.prop.UTC '
00543 
00544     >>> d = vDDDTypes.from_ical('20010101')
00545     >>> type(d)
00546     <type 'datetime.date'>
00547 
00548     >>> vDDDTypes.from_ical('P31D')
00549     datetime.timedelta(31)
00550 
00551     >>> vDDDTypes.from_ical('-P31D')
00552     datetime.timedelta(-31)
00553 
00554     Bad input
00555     >>> vDDDTypes(42)
00556     Traceback (most recent call last):
00557         ...
00558     ValueError: You must use datetime, date or timedelta
00559     """
00560 
00561     def __init__(self, dt):
00562         "Returns vDate from"
00563         wrong_type_used = 1
00564         for typ in (datetime, date, timedelta):
00565             if isinstance(dt, typ):
00566                 wrong_type_used = 0
00567         if wrong_type_used:
00568             raise ValueError ('You must use datetime, date or timedelta')
00569         self.dt = dt
00570 
00571     def ical(self):
00572         dt = self.dt
00573         if isinstance(dt, datetime):
00574             return vDatetime(dt).ical()
00575         elif isinstance(dt, date):
00576             return vDate(dt).ical()
00577         elif isinstance(dt, timedelta):
00578             return vDuration(dt).ical()
00579         else:
00580             raise ValueEror ('Unknown date type')
00581 
00582     def from_ical(ical):
00583         "Parses the data format from ical text format"
00584         u = ical.upper()
00585         if u.startswith('-P') or u.startswith('P'):
00586             return vDuration.from_ical(ical)
00587         try:
00588             return vDatetime.from_ical(ical)
00589         except:
00590             return vDate.from_ical(ical)
00591     from_ical = staticmethod(from_ical)
00592 
00593     def __str__(self):
00594         return self.ical()
00595 
00596 
00597 
00598 class vPeriod:
00599     """
00600     A precise period of time.
00601     One day in exact datetimes
00602     >>> per = (datetime(2000,1,1), datetime(2000,1,2))
00603     >>> p = vPeriod(per)
00604     >>> p.ical()
00605     '20000101T000000/20000102T000000'
00606 
00607     >>> per = (datetime(2000,1,1), timedelta(days=31))
00608     >>> p = vPeriod(per)
00609     >>> p.ical()
00610     '20000101T000000/P31D'
00611 
00612     Roundtrip
00613     >>> p = vPeriod.from_ical('20000101T000000/20000102T000000')
00614     >>> p
00615     (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 1, 2, 0, 0))
00616     >>> vPeriod(p).ical()
00617     '20000101T000000/20000102T000000'
00618 
00619     >>> vPeriod.from_ical('20000101T000000/P31D')
00620     (datetime.datetime(2000, 1, 1, 0, 0), datetime.timedelta(31))
00621 
00622     Roundtrip with absolute time
00623     >>> p = vPeriod.from_ical('20000101T000000Z/20000102T000000Z')
00624     >>> vPeriod(p).ical()
00625     '20000101T000000Z/20000102T000000Z'
00626 
00627     And an error
00628     >>> vPeriod.from_ical('20000101T000000/Psd31D')
00629     Traceback (most recent call last):
00630         ...
00631     ValueError: Expected period format, got: 20000101T000000/Psd31D
00632 
00633     Utc datetime
00634     >>> da_tz = FixedOffset(+1.0, 'da_DK')
00635     >>> start = datetime(2000,1,1, tzinfo=da_tz)
00636     >>> end = datetime(2000,1,2, tzinfo=da_tz)
00637     >>> per = (start, end)
00638     >>> vPeriod(per).ical()
00639     '19991231T235900Z/20000101T235900Z'
00640 
00641     >>> p = vPeriod((datetime(2000,1,1, tzinfo=da_tz), timedelta(days=31)))
00642     >>> p.ical()
00643     '19991231T235900Z/P31D'
00644     """
00645 
00646     def __init__(self, per):
00647         start, end_or_duration = per
00648         if not (isinstance(start, datetime) or isinstance(start, date)):
00649             raise ValueError('Start value MUST be a datetime or date instance')
00650         if not (isinstance(end_or_duration, datetime) or
00651                 isinstance(end_or_duration, date) or
00652                 isinstance(end_or_duration, timedelta)):
00653             raise ValueError('end_or_duration MUST be a datetime, date or timedelta instance')
00654         self.start = start
00655         self.end_or_duration = end_or_duration
00656         self.by_duration = 0
00657         if isinstance(end_or_duration, timedelta):
00658             self.by_duration = 1
00659             self.duration = end_or_duration
00660             self.end = self.start + self.duration
00661         else:
00662             self.end = end_or_duration
00663             self.duration = self.end - self.start
00664         if self.start > self.end:
00665             raise ValueError("Start time is greater than end time")
00666         self.params = Parameters()
00667 
00668     def __cmp__(self, other):
00669         if not isinstance(other, vPeriod):
00670             raise NotImplementedError(
00671                 'Cannot compare vPeriod with %s' % repr(other))
00672         return cmp((self.start, self.end), (other.start, other.end))
00673 
00674     def overlaps(self, other):
00675         if self.start > other.start:
00676             return other.overlaps(self)
00677         if self.start <= other.start < self.end:
00678             return True
00679         return False
00680 
00681     def ical(self):
00682         if self.by_duration:
00683             return '%s/%s' % (vDatetime(self.start).ical(), vDuration(self.duration).ical())
00684         return '%s/%s' % (vDatetime(self.start).ical(), vDatetime(self.end).ical())
00685 
00686     def from_ical(ical):
00687         "Parses the data format from ical text format"
00688         try:
00689             start, end_or_duration = ical.split('/')
00690             start = vDDDTypes.from_ical(start)
00691             end_or_duration = vDDDTypes.from_ical(end_or_duration)
00692             return (start, end_or_duration)
00693         except:
00694             raise ValueError, 'Expected period format, got: %s' % ical
00695     from_ical = staticmethod(from_ical)
00696 
00697     def __str__(self):
00698         return self.ical()
00699 
00700     def __repr__(self):
00701         if self.by_duration:
00702             p = (self.start, self.duration)
00703         else:
00704             p = (self.start, self.end)
00705         return 'vPeriod(%s)' % repr(p)
00706 
00707 class vWeekday(str):
00708     """
00709     This returns an unquoted weekday abbrevation
00710     >>> a = vWeekday('mo')
00711     >>> a.ical()
00712     'MO'
00713 
00714     >>> a = vWeekday('erwer')
00715     Traceback (most recent call last):
00716         ...
00717     ValueError: Expected weekday abbrevation, got: ERWER
00718 
00719     >>> vWeekday.from_ical('mo')
00720     'MO'
00721 
00722     >>> vWeekday.from_ical('+3mo')
00723     '+3MO'
00724 
00725     >>> vWeekday.from_ical('Saturday')
00726     Traceback (most recent call last):
00727         ...
00728     ValueError: Expected weekday abbrevation, got: Saturday
00729 
00730     >>> a = vWeekday('+mo')
00731     >>> a.ical()
00732     '+MO'
00733 
00734     >>> a = vWeekday('+3mo')
00735     >>> a.ical()
00736     '+3MO'
00737 
00738     >>> a = vWeekday('-tu')
00739     >>> a.ical()
00740     '-TU'
00741     """
00742 
00743     week_days = CaselessDict({"SU":0, "MO":1, "TU":2, "WE":3,
00744                               "TH":4, "FR":5, "SA":6})
00745 
00746     def __init__(self, *args, **kwargs):
00747         str.__init__(self, *args, **kwargs)
00748         match = WEEKDAY_RULE.match(self)
00749         if match is None:
00750             raise ValueError, 'Expected weekday abbrevation, got: %s' % self
00751         match = match.groupdict()
00752         sign = match['signal']
00753         weekday = match['weekday']
00754         relative = match['relative']
00755         if not weekday in vWeekday.week_days or sign not in '+-':
00756             raise ValueError, 'Expected weekday abbrevation, got: %s' % self
00757         self.relative = relative and int(relative) or None
00758         self.params = Parameters()
00759 
00760     def ical(self):
00761         return self.upper()
00762 
00763     def from_ical(ical):
00764         "Parses the data format from ical text format"
00765         try:
00766             return vWeekday(ical.upper())
00767         except:
00768             raise ValueError, 'Expected weekday abbrevation, got: %s' % ical
00769     from_ical = staticmethod(from_ical)
00770 
00771     def __str__(self):
00772         return self.ical()
00773 
00774 
00775 
00776 class vFrequency(str):
00777     """
00778     A simple class that catches illegal values.
00779     >>> f = vFrequency('bad test')
00780     Traceback (most recent call last):
00781         ...
00782     ValueError: Expected frequency, got: BAD TEST
00783     >>> vFrequency('daily').ical()
00784     'DAILY'
00785     >>> vFrequency('daily').from_ical('MONTHLY')
00786     'MONTHLY'
00787     """
00788 
00789     frequencies = CaselessDict({
00790         "SECONDLY":"SECONDLY",
00791         "MINUTELY":"MINUTELY",
00792         "HOURLY":"HOURLY",
00793         "DAILY":"DAILY",
00794         "WEEKLY":"WEEKLY",
00795         "MONTHLY":"MONTHLY",
00796         "YEARLY":"YEARLY",
00797     })
00798 
00799     def __init__(self, *args, **kwargs):
00800         str.__init__(self, *args, **kwargs)
00801         if not self in vFrequency.frequencies:
00802             raise ValueError, 'Expected frequency, got: %s' % self
00803         self.params = Parameters()
00804 
00805     def ical(self):
00806         return self.upper()
00807 
00808     def from_ical(ical):
00809         "Parses the data format from ical text format"
00810         try:
00811             return vFrequency(ical.upper())
00812         except:
00813             raise ValueError, 'Expected weekday abbrevation, got: %s' % ical
00814     from_ical = staticmethod(from_ical)
00815 
00816     def __str__(self):
00817         return self.ical()
00818 
00819 
00820 
00821 class vRecur(CaselessDict):
00822     """
00823     Let's see how close we can get to one from the rfc:
00824     FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30
00825 
00826     >>> r = dict(freq='yearly', interval=2)
00827     >>> r['bymonth'] = 1
00828     >>> r['byday'] = 'su'
00829     >>> r['byhour'] = [8,9]
00830     >>> r['byminute'] = 30
00831     >>> r = vRecur(r)
00832     >>> r.ical()
00833     'BYHOUR=8,9;BYDAY=SU;BYMINUTE=30;BYMONTH=1;FREQ=YEARLY;INTERVAL=2'
00834 
00835     >>> r = vRecur(FREQ='yearly', INTERVAL=2)
00836     >>> r['BYMONTH'] = 1
00837     >>> r['BYDAY'] = 'su'
00838     >>> r['BYHOUR'] = [8,9]
00839     >>> r['BYMINUTE'] = 30
00840     >>> r.ical()
00841     'BYDAY=SU;BYMINUTE=30;BYMONTH=1;INTERVAL=2;FREQ=YEARLY;BYHOUR=8,9'
00842 
00843     >>> r = vRecur(freq='DAILY', count=10)
00844     >>> r['bysecond'] = [0, 15, 30, 45]
00845     >>> r.ical()
00846     'COUNT=10;FREQ=DAILY;BYSECOND=0,15,30,45'
00847 
00848     >>> r = vRecur(freq='DAILY', until=datetime(2005,1,1,12,0,0))
00849     >>> r.ical()
00850     'FREQ=DAILY;UNTIL=20050101T120000'
00851 
00852     How do we fare with regards to parsing?
00853     >>> r = vRecur.from_ical('FREQ=DAILY;INTERVAL=2;COUNT=10')
00854     >>> r
00855     {'COUNT': [10], 'FREQ': ['DAILY'], 'INTERVAL': [2]}
00856     >>> vRecur(r).ical()
00857     'COUNT=10;FREQ=DAILY;INTERVAL=2'
00858 
00859     >>> r = vRecur.from_ical('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=-SU;BYHOUR=8,9;BYMINUTE=30')
00860     >>> r
00861     {'BYHOUR': [8, 9], 'BYDAY': ['-SU'], 'BYMINUTE': [30], 'BYMONTH': [1], 'FREQ': ['YEARLY'], 'INTERVAL': [2]}
00862     >>> vRecur(r).ical()
00863     'BYDAY=-SU;BYMINUTE=30;INTERVAL=2;BYMONTH=1;FREQ=YEARLY;BYHOUR=8,9'
00864 
00865     Some examples from the spec
00866 
00867     >>> r = vRecur.from_ical('FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1')
00868     >>> vRecur(r).ical()
00869     'BYSETPOS=-1;FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR'
00870 
00871     >>> r = vRecur.from_ical('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30')
00872     >>> vRecur(r).ical()
00873     'BYDAY=SU;BYMINUTE=30;INTERVAL=2;BYMONTH=1;FREQ=YEARLY;BYHOUR=8,9'
00874 
00875     and some errors
00876     >>> r = vRecur.from_ical('BYDAY=12')
00877     Traceback (most recent call last):
00878         ...
00879     ValueError: Error in recurrence rule: BYDAY=12
00880 
00881     """
00882 
00883     frequencies = ["SECONDLY",  "MINUTELY", "HOURLY", "DAILY", "WEEKLY",
00884                    "MONTHLY", "YEARLY"]
00885 
00886     types = CaselessDict({
00887         'COUNT':vInt,
00888         'INTERVAL':vInt,
00889         'BYSECOND':vInt,
00890         'BYMINUTE':vInt,
00891         'BYHOUR':vInt,
00892         'BYMONTHDAY':vInt,
00893         'BYYEARDAY':vInt,
00894         'BYMONTH':vInt,
00895         'UNTIL':vDDDTypes,
00896         'BYSETPOS':vInt,
00897         'WKST':vWeekday,
00898         'BYDAY':vWeekday,
00899         'FREQ':vFrequency
00900     })
00901 
00902     def __init__(self, *args, **kwargs):
00903         CaselessDict.__init__(self, *args, **kwargs)
00904         self.params = Parameters()
00905 
00906     def ical(self):
00907         # SequenceTypes
00908         result = []
00909         for key, vals in self.items():
00910             typ = self.types[key]
00911             if not type(vals) in SequenceTypes:
00912                 vals = [vals]
00913             vals = ','.join([typ(val).ical() for val in vals])
00914             result.append('%s=%s' % (key, vals))
00915         return ';'.join(result)
00916 
00917     def parse_type(key, values):
00918         # integers
00919         parser = vRecur.types.get(key, vText)
00920         return [parser.from_ical(v) for v in values.split(',')]
00921     parse_type = staticmethod(parse_type)
00922 
00923     def from_ical(ical):
00924         "Parses the data format from ical text format"
00925         try:
00926             recur = vRecur()
00927             for pairs in ical.split(';'):
00928                 key, vals = pairs.split('=')
00929                 recur[key] = vRecur.parse_type(key, vals)
00930             return dict(recur)
00931         except:
00932             raise ValueError, 'Error in recurrence rule: %s' % ical
00933     from_ical = staticmethod(from_ical)
00934 
00935     def __str__(self):
00936         return self.ical()
00937 
00938 
00939 
00940 class vText(unicode):
00941     """
00942     Simple text
00943     >>> t = vText(u'Simple text')
00944     >>> t.ical()
00945     'Simple text'
00946 
00947     Escaped text
00948     >>> t = vText('Text ; with escaped, chars')
00949     >>> t.ical()
00950     'Text \\\\; with escaped\\\\, chars'
00951 
00952     Escaped newlines
00953     >>> vText('Text with escaped\N chars').ical()
00954     'Text with escaped\\\\n chars'
00955 
00956     If you pass a unicode object, it will be utf-8 encoded. As this is the
00957     (only) standard that RFC 2445 support.
00958 
00959     >>> t = vText(u'international chars   ')
00960     >>> t.ical()
00961     'international chars \\xc3\\xa6\\xc3\\xb8\\xc3\\xa5 \\xc3\\x86\\xc3\\x98\\xc3\\x85 \\xc3\\xbc'
00962 
00963     Unicode is converted to utf-8
00964     >>> t = vText(u'international   ')
00965     >>> str(t)
00966     'international \\xc3\\xa6 \\xc3\\xb8 \\xc3\\xa5'
00967 
00968     and parsing?
00969     >>> vText.from_ical('Text \\; with escaped\\, chars')
00970     u'Text ; with escaped, chars'
00971 
00972     >>> print vText.from_ical('A string with\\; some\\\\ characters in\\Nit')
00973     A string with; some\\ characters in
00974     it
00975     """
00976 
00977     encoding = 'utf-8'
00978 
00979     def __init__(self, *args, **kwargs):
00980         unicode.__init__(self, *args, **kwargs)
00981         self.params = Parameters()
00982 
00983     def escape(self, value):
00984         """
00985         Format value according to iCalendar TEXT escaping rules.
00986         """
00987         return (value.replace('\N', '\n')
00988                      .replace('\\', '\\\\')
00989                      .replace(';', r'\;')
00990                      .replace(',', r'\,')
00991                      .replace('\r\n', r'\n')
00992                      .replace('\n', r'\n')
00993                      )
00994 
00995     def __repr__(self):
00996         return u"vText(%s)" % unicode.__repr__(self)
00997 
00998     def ical(self):
00999         return self.escape(self).encode(self.encoding)
01000 
01001     def from_ical(ical):
01002         "Parses the data format from ical text format"
01003         try:
01004             ical = (ical.replace(r'\N', r'\n')
01005                         .replace(r'\r\n', '\n')
01006                         .replace(r'\n', '\n')
01007                         .replace(r'\,', ',')
01008                         .replace(r'\;', ';')
01009                         .replace('\\\\', '\\'))
01010             return ical.decode(vText.encoding)
01011         except:
01012             raise ValueError, 'Expected ical text, got: %s' % ical
01013     from_ical = staticmethod(from_ical)
01014 
01015     def __str__(self):
01016         return self.ical()
01017 
01018 
01019 
01020 class vTime(time):
01021     """
01022     A subclass of datetime, that renders itself in the iCalendar time
01023     format.
01024     >>> dt = vTime(12, 30, 0)
01025     >>> dt.ical()
01026     '123000'
01027 
01028     >>> vTime.from_ical('123000')
01029     datetime.time(12, 30)
01030 
01031     We should also fail, right?
01032     >>> vTime.from_ical('263000')
01033     Traceback (most recent call last):
01034         ...
01035     ValueError: Expected time, got: 263000
01036     """
01037 
01038     def __init__(self, *args, **kwargs):
01039         time.__init__(self, *args, **kwargs)
01040         self.params = Parameters()
01041 
01042     def ical(self):
01043         return self.strftime("%H%M%S")
01044 
01045     def from_ical(ical):
01046         "Parses the data format from ical text format"
01047         try:
01048             timetuple = map(int, (ical[:2],ical[2:4],ical[4:6]))
01049             return time(*timetuple)
01050         except:
01051             raise ValueError, 'Expected time, got: %s' % ical
01052     from_ical = staticmethod(from_ical)
01053 
01054     def __str__(self):
01055         return self.ical()
01056 
01057 
01058 
01059 class vUri(str):
01060     """
01061     Uniform resource identifier is basically just an unquoted string.
01062     >>> u = vUri('http://www.example.com/')
01063     >>> u.ical()
01064     'http://www.example.com/'
01065     >>> vUri.from_ical('http://www.example.com/') # doh!
01066     'http://www.example.com/'
01067     """
01068 
01069     def __init__(self, *args, **kwargs):
01070         str.__init__(self, *args, **kwargs)
01071         self.params = Parameters()
01072 
01073     def ical(self):
01074         return str(self)
01075 
01076     def from_ical(ical):
01077         "Parses the data format from ical text format"
01078         try:
01079             return str(ical)
01080         except:
01081             raise ValueError, 'Expected , got: %s' % ical
01082     from_ical = staticmethod(from_ical)
01083 
01084     def __str__(self):
01085         return str.__str__(self)
01086 
01087 
01088 
01089 class vGeo:
01090     """
01091     A special type that is only indirectly defined in the rfc.
01092 
01093     >>> g = vGeo((1.2, 3.0))
01094     >>> g.ical()
01095     '1.2;3.0'
01096 
01097     >>> g = vGeo.from_ical('37.386013;-122.082932')
01098     >>> g
01099     (37.386012999999998, -122.082932)
01100 
01101     >>> vGeo(g).ical()
01102     '37.386013;-122.082932'
01103 
01104     >>> vGeo('g').ical()
01105     Traceback (most recent call last):
01106         ...
01107     ValueError: Input must be (float, float) for latitude and longitude
01108     """
01109 
01110     def __init__(self, geo):
01111         try:
01112             latitude, longitude = geo
01113             latitude = float(latitude)
01114             longitude = float(longitude)
01115         except:
01116             raise ValueError('Input must be (float, float) for latitude and longitude')
01117         self.latitude = latitude
01118         self.longitude = longitude
01119         self.params = Parameters()
01120 
01121     def ical(self):
01122         return '%s;%s' % (self.latitude, self.longitude)
01123 
01124     def from_ical(ical):
01125         "Parses the data format from ical text format"
01126         try:
01127             latitude, longitude = ical.split(';')
01128             return (float(latitude), float(longitude))
01129         except:
01130             raise ValueError, "Expected 'float;float' , got: %s" % ical
01131     from_ical = staticmethod(from_ical)
01132 
01133     def __str__(self):
01134         return self.ical()
01135 
01136 
01137 
01138 class vUTCOffset:
01139     """
01140     Renders itself as a utc offset
01141 
01142     >>> u = vUTCOffset(timedelta(hours=2))
01143     >>> u.ical()
01144     '+0200'
01145 
01146     >>> u = vUTCOffset(timedelta(hours=-5))
01147     >>> u.ical()
01148     '-0500'
01149 
01150     >>> u = vUTCOffset(timedelta())
01151     >>> u.ical()
01152     '0000'
01153 
01154     >>> u = vUTCOffset(timedelta(minutes=-30))
01155     >>> u.ical()
01156     '-0030'
01157 
01158     >>> u = vUTCOffset(timedelta(hours=2, minutes=-30))
01159     >>> u.ical()
01160     '+0130'
01161 
01162     >>> u = vUTCOffset(timedelta(hours=1, minutes=30))
01163     >>> u.ical()
01164     '+0130'
01165 
01166     Parsing
01167 
01168     >>> vUTCOffset.from_ical('0000')
01169     datetime.timedelta(0)
01170 
01171     >>> vUTCOffset.from_ical('-0030')
01172     datetime.timedelta(-1, 84600)
01173 
01174     >>> vUTCOffset.from_ical('+0200')
01175     datetime.timedelta(0, 7200)
01176 
01177     >>> o = vUTCOffset.from_ical('+0230')
01178     >>> vUTCOffset(o).ical()
01179     '+0230'
01180 
01181     And a few failures
01182     >>> vUTCOffset.from_ical('+323k')
01183     Traceback (most recent call last):
01184         ...
01185     ValueError: Expected utc offset, got: +323k
01186 
01187     >>> vUTCOffset.from_ical('+2400')
01188     Traceback (most recent call last):
01189         ...
01190     ValueError: Offset must be less than 24 hours, was +2400
01191     """
01192 
01193     def __init__(self, td):
01194         if not isinstance(td, timedelta):
01195             raise ValueError('Offset value MUST be a timedelta instance')
01196         self.td = td
01197         self.params = Parameters()
01198 
01199     def ical(self):
01200         td = self.td
01201         day_in_minutes = (td.days * 24 * 60)
01202         seconds_in_minutes = td.seconds // 60
01203         total_minutes = day_in_minutes + seconds_in_minutes
01204         if total_minutes == 0:
01205             sign = '%s'
01206         elif total_minutes < 0:
01207             sign = '-%s'
01208         else:
01209             sign = '+%s'
01210         hours = abs(total_minutes) // 60
01211         minutes = total_minutes % 60
01212         duration = '%02i%02i' % (hours, minutes)
01213         return sign % duration
01214 
01215     def from_ical(ical):
01216         "Parses the data format from ical text format"
01217         try:
01218             sign, hours, minutes = (ical[-5:-4], int(ical[-4:-2]), int(ical[-2:]))
01219             offset = timedelta(hours=hours, minutes=minutes)
01220         except:
01221             raise ValueError, 'Expected utc offset, got: %s' % ical
01222         if offset >= timedelta(hours=24):
01223             raise ValueError, 'Offset must be less than 24 hours, was %s' % ical
01224         if sign == '-':
01225             return -offset
01226         return offset
01227     from_ical = staticmethod(from_ical)
01228 
01229     def __str__(self):
01230         return self.ical()
01231 
01232 
01233 
01234 class vInline(str):
01235     """
01236     This is an especially dumb class that just holds raw unparsed text and has
01237     parameters. Conversion of inline values are handled by the Component class,
01238     so no further processing is needed.
01239 
01240     >>> vInline('Some text')
01241     'Some text'
01242 
01243     >>> vInline.from_ical('Some text')
01244     'Some text'
01245 
01246     >>> t2 = vInline('other text')
01247     >>> t2.params['cn'] = 'Test Osterone'
01248     >>> t2.params
01249     Parameters({'CN': 'Test Osterone'})
01250 
01251     """
01252 
01253     def __init__(self,obj):
01254         self.obj = obj
01255         self.params = Parameters()
01256 
01257     def ical(self):
01258         return str(self)
01259 
01260     def from_ical(ical):
01261         return str(ical)
01262     from_ical = staticmethod(from_ical)
01263 
01264     def __str__(self):
01265         return str(self.obj)
01266 
01267 
01268 class TypesFactory(CaselessDict):
01269     """
01270     All Value types defined in rfc 2445 are registered in this factory class. To
01271     get a type you can use it like this.
01272     >>> factory = TypesFactory()
01273     >>> datetime_parser = factory['date-time']
01274     >>> dt = datetime_parser(datetime(2001, 1, 1))
01275     >>> dt.ical()
01276     '20010101T000000'
01277 
01278     A typical use is when the parser tries to find a content type and use text
01279     as the default
01280     >>> value = '20050101T123000'
01281     >>> value_type = 'date-time'
01282     >>> typ = factory.get(value_type, 'text')
01283     >>> typ.from_ical(value)
01284     datetime.datetime(2005, 1, 1, 12, 30)
01285 
01286     It can also be used to directly encode property and parameter values
01287     >>> comment = factory.ical('comment', u'by Rasmussen, Max Mller')
01288     >>> str(comment)
01289     'by Rasmussen\\\\, Max M\\xc3\\xb8ller'
01290     >>> factory.ical('priority', 1)
01291     '1'
01292     >>> factory.ical('cn', u'Rasmussen, Max Mller')
01293     'Rasmussen\\\\, Max M\\xc3\\xb8ller'
01294 
01295     >>> factory.from_ical('cn', 'Rasmussen\\\\, Max M\\xc3\\xb8ller')
01296     u'Rasmussen, Max M\\xf8ller'
01297 
01298     The value and parameter names don't overlap. So one factory is enough for
01299     both kinds.
01300     """
01301 
01302     def __init__(self, *args, **kwargs):
01303         "Set keys to upper for initial dict"
01304         CaselessDict.__init__(self, *args, **kwargs)
01305         self['binary'] = vBinary
01306         self['boolean'] = vBoolean
01307         self['cal-address'] = vCalAddress
01308         self['date'] = vDDDTypes
01309         self['date-time'] = vDDDTypes
01310         self['duration'] = vDDDTypes
01311         self['float'] = vFloat
01312         self['integer'] = vInt
01313         self['period'] = vPeriod
01314         self['recur'] = vRecur
01315         self['text'] = vText
01316         self['time'] = vTime
01317         self['uri'] = vUri
01318         self['utc-offset'] = vUTCOffset
01319         self['geo'] = vGeo
01320         self['inline'] = vInline
01321 
01322 
01323     #################################################
01324     # Property types
01325 
01326     # These are the default types
01327     types_map = CaselessDict({
01328         ####################################
01329         # Property valye types
01330         # Calendar Properties
01331         'calscale' : 'text',
01332         'method' : 'text',
01333         'prodid' : 'text',
01334         'version' : 'text',
01335         # Descriptive Component Properties
01336         'attach' : 'uri',
01337         'categories' : 'text',
01338         'class' : 'text',
01339         'comment' : 'text',
01340         'description' : 'text',
01341         'geo' : 'geo',
01342         'location' : 'text',
01343         'percent-complete' : 'integer',
01344         'priority' : 'integer',
01345         'resources' : 'text',
01346         'status' : 'text',
01347         'summary' : 'text',
01348         # Date and Time Component Properties
01349         'completed' : 'date-time',
01350         'dtend' : 'date-time',
01351         'due' : 'date-time',
01352         'dtstart' : 'date-time',
01353         'duration' : 'duration',
01354         'freebusy' : 'period',
01355         'transp' : 'text',
01356         # Time Zone Component Properties
01357         'tzid' : 'text',
01358         'tzname' : 'text',
01359         'tzoffsetfrom' : 'utc-offset',
01360         'tzoffsetto' : 'utc-offset',
01361         'tzurl' : 'uri',
01362         # Relationship Component Properties
01363         'attendee' : 'cal-address',
01364         'contact' : 'text',
01365         'organizer' : 'cal-address',
01366         'recurrence-id' : 'date-time',
01367         'related-to' : 'text',
01368         'url' : 'uri',
01369         'uid' : 'text',
01370         # Recurrence Component Properties
01371         'exdate' : 'date-time',
01372         'exrule' : 'recur',
01373         'rdate' : 'date-time',
01374         'rrule' : 'recur',
01375         # Alarm Component Properties
01376         'action' : 'text',
01377         'repeat' : 'integer',
01378         'trigger' : 'duration',
01379         # Change Management Component Properties
01380         'created' : 'date-time',
01381         'dtstamp' : 'date-time',
01382         'last-modified' : 'date-time',
01383         'sequence' : 'integer',
01384         # Miscellaneous Component Properties
01385         'request-status' : 'text',
01386         ####################################
01387         # parameter types (luckilly there is no name overlap)
01388         'altrep' : 'uri',
01389         'cn' : 'text',
01390         'cutype' : 'text',
01391         'delegated-from' : 'cal-address',
01392         'delegated-to' : 'cal-address',
01393         'dir' : 'uri',
01394         'encoding' : 'text',
01395         'fmttype' : 'text',
01396         'fbtype' : 'text',
01397         'language' : 'text',
01398         'member' : 'cal-address',
01399         'partstat' : 'text',
01400         'range' : 'text',
01401         'related' : 'text',
01402         'reltype' : 'text',
01403         'role' : 'text',
01404         'rsvp' : 'boolean',
01405         'sent-by' : 'cal-address',
01406         'tzid' : 'text',
01407         'value' : 'text',
01408     })
01409 
01410 
01411     def for_property(self, name):
01412         "Returns a the default type for a property or parameter"
01413         return self[self.types_map.get(name, 'text')]
01414 
01415     def ical(self, name, value):
01416         """
01417         Encodes a named value from a primitive python type to an
01418         icalendar encoded string.
01419         """
01420         type_class = self.for_property(name)
01421         return type_class(value).ical()
01422 
01423     def from_ical(self, name, value):
01424         """
01425         Decodes a named property or parameter value from an icalendar encoded
01426         string to a primitive python type.
01427         """
01428         type_class = self.for_property(name)
01429         decoded = type_class.from_ical(str(value))
01430         return decoded