kopia lustrzana https://github.com/collective/icalendar
remove unused tests, move examples.rst to docs/usage.rst, pep8 everything
rodzic
6ded747631
commit
a08849e099
|
|
@ -1,45 +0,0 @@
|
|||
========
|
||||
Examples
|
||||
========
|
||||
|
||||
To open and parse a file::
|
||||
|
||||
>>> from icalendar import Calendar, Event
|
||||
>>> cal = Calendar.from_ical(open('test.ics','rb').read())
|
||||
>>> cal
|
||||
VCALENDAR({'VERSION': vText(u'2.0'), 'METHOD': vText(u'Request'), 'PRODID': vText(u'-//My product//mxm.dk/')})
|
||||
|
||||
>>> for component in cal.walk():
|
||||
... component.name
|
||||
'VCALENDAR'
|
||||
'VEVENT'
|
||||
'VEVENT'
|
||||
|
||||
To create a calendar and write it to disk::
|
||||
|
||||
>>> cal = Calendar()
|
||||
>>> from datetime import datetime
|
||||
>>> cal.add('prodid', '-//My calendar product//mxm.dk//')
|
||||
>>> cal.add('version', '2.0')
|
||||
|
||||
>>> import pytz
|
||||
>>> event = Event()
|
||||
>>> event.add('summary', 'Python meeting about calendaring')
|
||||
>>> event.add('dtstart', datetime(2005,4,4,8,0,0,tzinfo=pytz.utc))
|
||||
>>> event.add('dtend', datetime(2005,4,4,10,0,0,tzinfo=pytz.utc))
|
||||
>>> event.add('dtstamp', datetime(2005,4,4,0,10,0,tzinfo=pytz.utc))
|
||||
>>> event['uid'] = '20050115T101010/27346262376@mxm.dk'
|
||||
>>> event.add('priority', 5)
|
||||
|
||||
>>> cal.add_component(event)
|
||||
|
||||
>>> f = open('example.ics', 'wb')
|
||||
>>> f.write(cal.to_ical())
|
||||
>>> f.close()
|
||||
|
||||
More documentation
|
||||
==================
|
||||
|
||||
Have a look at the doctests in the tests directory of the package to get more
|
||||
examples. All modules and classes also have doctests that show how they work.
|
||||
There is also an `interfaces.py` file which describes the API.
|
||||
|
|
@ -9,7 +9,7 @@ Contents
|
|||
|
||||
about
|
||||
install
|
||||
examples
|
||||
usage
|
||||
RFC 5545 <rfc5545/index>
|
||||
changelog
|
||||
credits
|
||||
|
|
|
|||
|
|
@ -283,5 +283,10 @@ Write to disk::
|
|||
>>> f.write(cal.to_ical())
|
||||
>>> f.close()
|
||||
|
||||
XXX We should check whether the write succeeded here..
|
||||
|
||||
More documentation
|
||||
==================
|
||||
|
||||
Have a look at the tests of this package to get more examples.
|
||||
All modules and classes docstrings, which document how they work.
|
||||
There is also an `interfaces.py` file which describes the API.
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
files according to rfc2445.
|
||||
|
||||
These are the defined components.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import pytz
|
||||
|
|
@ -28,7 +27,6 @@ from .parser_tools import DEFAULT_ENCODING
|
|||
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.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
@ -59,15 +57,15 @@ 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.
|
||||
|
||||
"""
|
||||
|
||||
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')
|
||||
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')
|
||||
ignore_exceptions = False # if True, and we cannot parse this
|
||||
# component, we will silently ignore
|
||||
# it, rather than let the exception
|
||||
|
|
@ -76,12 +74,12 @@ class Component(CaselessDict):
|
|||
|
||||
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.
|
||||
self.is_broken = False # True iff we ignored an exception while parsing a property
|
||||
self.subcomponents = [] # Components can be nested.
|
||||
self.is_broken = False # True if we ignored an exception while
|
||||
# parsing a property
|
||||
|
||||
#def is_compliant(self, name):
|
||||
# """Returns True is the given property name is compliant with the
|
||||
|
|
@ -93,13 +91,11 @@ class Component(CaselessDict):
|
|||
# """
|
||||
# return name in not_compliant
|
||||
|
||||
|
||||
#############################
|
||||
# handling of property values
|
||||
|
||||
def _encode(self, name, value, cond=1):
|
||||
"""Conditional convertion of values.
|
||||
|
||||
"""
|
||||
if not cond:
|
||||
return value
|
||||
|
|
@ -124,7 +120,6 @@ class Component(CaselessDict):
|
|||
|
||||
def add(self, name, value, encode=1):
|
||||
"""Add a property.
|
||||
|
||||
"""
|
||||
if isinstance(value, datetime) and\
|
||||
name.lower() in ('dtstamp', 'created', 'last-modified'):
|
||||
|
|
@ -148,7 +143,6 @@ class Component(CaselessDict):
|
|||
|
||||
def _decode(self, name, value):
|
||||
"""Internal for decoding property values.
|
||||
|
||||
"""
|
||||
|
||||
# TODO: Currently the decoded method calls the icalendar.prop instances
|
||||
|
|
@ -167,7 +161,6 @@ class Component(CaselessDict):
|
|||
|
||||
def decoded(self, name, default=_marker):
|
||||
"""Returns decoded value of property.
|
||||
|
||||
"""
|
||||
# XXX: fail. what's this function supposed to do in the end?
|
||||
# -rnix
|
||||
|
|
@ -189,7 +182,6 @@ class Component(CaselessDict):
|
|||
|
||||
def get_inline(self, name, decode=1):
|
||||
"""Returns a list of values (split on comma).
|
||||
|
||||
"""
|
||||
vals = [v.strip('" ') for v in q_split(self[name])]
|
||||
if decode:
|
||||
|
|
@ -199,7 +191,6 @@ class Component(CaselessDict):
|
|||
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]
|
||||
|
|
@ -210,13 +201,11 @@ class Component(CaselessDict):
|
|||
|
||||
def add_component(self, component):
|
||||
"""Add a subcomponent to this component.
|
||||
|
||||
"""
|
||||
self.subcomponents.append(component)
|
||||
|
||||
def _walk(self, name):
|
||||
"""Walk to given component.
|
||||
|
||||
"""
|
||||
result = []
|
||||
if name is None or self.name == name:
|
||||
|
|
@ -228,7 +217,6 @@ class Component(CaselessDict):
|
|||
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()
|
||||
|
|
@ -240,7 +228,6 @@ class Component(CaselessDict):
|
|||
def property_items(self, recursive=True):
|
||||
"""Returns properties in this component and subcomponents as:
|
||||
[(name, value), ...]
|
||||
|
||||
"""
|
||||
vText = types_factory['text']
|
||||
properties = [('BEGIN', vText(self.name).to_ical())]
|
||||
|
|
@ -263,11 +250,10 @@ class Component(CaselessDict):
|
|||
@classmethod
|
||||
def from_ical(cls, st, multiple=False):
|
||||
"""Populates the component recursively from a string.
|
||||
|
||||
"""
|
||||
stack = [] # a stack of components
|
||||
stack = [] # a stack of components
|
||||
comps = []
|
||||
for line in Contentlines.from_ical(st): # raw parsing
|
||||
for line in Contentlines.from_ical(st): # raw parsing
|
||||
if not line:
|
||||
continue
|
||||
name, params, vals = line.parts()
|
||||
|
|
@ -279,7 +265,7 @@ class Component(CaselessDict):
|
|||
c_name = vals.upper()
|
||||
c_class = component_factory.get(c_name, cls)
|
||||
component = c_class()
|
||||
if not getattr(component, 'name', ''): # undefined components
|
||||
if not getattr(component, 'name', ''): # undefined components
|
||||
component.name = c_name
|
||||
stack.append(component)
|
||||
# check for end of event
|
||||
|
|
@ -287,7 +273,7 @@ class Component(CaselessDict):
|
|||
# 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
|
||||
if not stack: # we are at the end
|
||||
comps.append(component)
|
||||
else:
|
||||
if not component.is_broken:
|
||||
|
|
@ -338,7 +324,7 @@ class Component(CaselessDict):
|
|||
for name, value in self.property_items():
|
||||
cl = self.content_line(name, value)
|
||||
contentlines.append(cl)
|
||||
contentlines.append('') # remember the empty string in the end
|
||||
contentlines.append('') # remember the empty string in the end
|
||||
return contentlines
|
||||
|
||||
def to_ical(self):
|
||||
|
|
@ -452,7 +438,6 @@ class Alarm(Component):
|
|||
|
||||
class Calendar(Component):
|
||||
"""This is the base object for an iCalendar file.
|
||||
|
||||
"""
|
||||
name = 'VCALENDAR'
|
||||
canonical_order = ('VERSION', 'PRODID', 'CALSCALE', 'METHOD',)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from .parser_tools import to_unicode, data_encode
|
|||
def canonsort_keys(keys, canonical_order=None):
|
||||
"""Sorts leading keys according to canonical_order. Keys not specified in
|
||||
canonical_order will appear alphabetically at the end.
|
||||
|
||||
"""
|
||||
canonical_map = dict((k, i) for i, k in enumerate(canonical_order or []))
|
||||
head = [k for k in keys if k in canonical_map]
|
||||
|
|
@ -16,7 +15,6 @@ def canonsort_keys(keys, canonical_order=None):
|
|||
|
||||
def canonsort_items(dict1, canonical_order=None):
|
||||
"""Returns a list of items from dict1, sorted by canonical_order.
|
||||
|
||||
"""
|
||||
return [(k, dict1[k]) for \
|
||||
k in canonsort_keys(dict1.keys(), canonical_order)]
|
||||
|
|
@ -25,7 +23,6 @@ def canonsort_items(dict1, canonical_order=None):
|
|||
class CaselessDict(dict):
|
||||
"""A dictionary that isn't case sensitive, and only uses strings as keys.
|
||||
Values retain their case.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
@ -74,9 +71,7 @@ class CaselessDict(dict):
|
|||
return dict.__contains__(self, key.upper())
|
||||
|
||||
def update(self, indict):
|
||||
"""
|
||||
Multiple keys where key1.upper() == key2.upper() will be lost.
|
||||
"""
|
||||
# Multiple keys where key1.upper() == key2.upper() will be lost.
|
||||
for key, value in indict.items(): # TODO optimize in python 2
|
||||
self[key] = value
|
||||
|
||||
|
|
@ -91,15 +86,13 @@ class CaselessDict(dict):
|
|||
canonical_order = None
|
||||
|
||||
def sorted_keys(self):
|
||||
"""
|
||||
Sorts keys according to the canonical_order for the derived class.
|
||||
"""Sorts keys according to the canonical_order for the derived class.
|
||||
Keys not specified in canonical_order will appear at the end.
|
||||
"""
|
||||
return canonsort_keys(self.keys(), self.canonical_order)
|
||||
|
||||
def sorted_items(self):
|
||||
"""
|
||||
Sorts items according to the canonical_order for the derived class.
|
||||
"""Sorts items according to the canonical_order for the derived class.
|
||||
Items not specified in canonical_order will appear at the end.
|
||||
"""
|
||||
return canonsort_items(self, self.canonical_order)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from .parser_tools import (
|
|||
)
|
||||
import icalendar.compat as compat
|
||||
|
||||
|
||||
def escape_char(text):
|
||||
"""Format value according to iCalendar TEXT escaping rules.
|
||||
"""
|
||||
|
|
@ -163,15 +164,13 @@ def q_join(lst, sep=','):
|
|||
|
||||
|
||||
class Parameters(CaselessDict):
|
||||
"""
|
||||
Parser and generator of Property parameter strings. It knows nothing of
|
||||
"""Parser and generator of Property parameter strings. It knows nothing of
|
||||
datatypes. Its main concern is textual structure.
|
||||
"""
|
||||
|
||||
def params(self):
|
||||
"""
|
||||
in rfc2445 keys are called parameters, so this is to be consitent with
|
||||
the naming conventions
|
||||
"""In rfc2445 keys are called parameters, so this is to be consitent
|
||||
with the naming conventions.
|
||||
"""
|
||||
return self.keys()
|
||||
|
||||
|
|
@ -211,7 +210,7 @@ class Parameters(CaselessDict):
|
|||
|
||||
@classmethod
|
||||
def from_ical(cls, st, strict=False):
|
||||
"Parses the parameter format from ical text format"
|
||||
"""Parses the parameter format from ical text format."""
|
||||
|
||||
# parse into strings
|
||||
result = cls()
|
||||
|
|
@ -263,7 +262,6 @@ def unsescape_string(val):
|
|||
class Contentline(compat.unicode_type):
|
||||
"""A content line is basically a string that can be folded and parsed into
|
||||
parts.
|
||||
|
||||
"""
|
||||
def __new__(cls, value, strict=False, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
|
|
@ -339,7 +337,7 @@ class Contentline(compat.unicode_type):
|
|||
return cls(uFOLD.sub('', ical), strict=strict)
|
||||
|
||||
def to_ical(self):
|
||||
"""Long content lines are folded so they are less than 75 characters.
|
||||
"""Long content lines are folded so they are less than 75 characters
|
||||
wide.
|
||||
"""
|
||||
return foldline(self).encode(DEFAULT_ENCODING)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ primitive Python datatype. So it should allways be true that:
|
|||
|
||||
These types are mainly used for parsing and file generation. But you can set
|
||||
them directly.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import re
|
||||
|
|
@ -130,7 +129,6 @@ class LocalTimezone(tzinfo):
|
|||
|
||||
class vBinary(object):
|
||||
"""Binary property values are base 64 encoded.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, obj):
|
||||
|
|
@ -153,7 +151,6 @@ class vBinary(object):
|
|||
|
||||
class vBoolean(int):
|
||||
"""Returns specific string according to state.
|
||||
|
||||
"""
|
||||
BOOL_MAP = CaselessDict(true=True, false=False)
|
||||
|
||||
|
|
@ -177,7 +174,6 @@ class vBoolean(int):
|
|||
|
||||
class vCalAddress(compat.unicode_type):
|
||||
"""This just returns an unquoted string.
|
||||
|
||||
"""
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
|
|
@ -198,7 +194,6 @@ class vCalAddress(compat.unicode_type):
|
|||
|
||||
class vFloat(float):
|
||||
"""Just a float.
|
||||
|
||||
"""
|
||||
def __new__(cls, *args, **kwargs):
|
||||
self = super(vFloat, cls).__new__(cls, *args, **kwargs)
|
||||
|
|
@ -218,7 +213,6 @@ class vFloat(float):
|
|||
|
||||
class vInt(int):
|
||||
"""Just an int.
|
||||
|
||||
"""
|
||||
def __new__(cls, *args, **kwargs):
|
||||
self = super(vInt, cls).__new__(cls, *args, **kwargs)
|
||||
|
|
@ -238,7 +232,6 @@ class vInt(int):
|
|||
|
||||
class vDDDLists(object):
|
||||
"""A list of vDDDTypes values.
|
||||
|
||||
"""
|
||||
def __init__(self, dt_list):
|
||||
if not hasattr(dt_list, '__iter__'):
|
||||
|
|
@ -260,7 +253,6 @@ class vDDDLists(object):
|
|||
dts_ical = (dt.to_ical() for dt in self.dts)
|
||||
return b",".join(dts_ical)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical, timezone=None):
|
||||
out = []
|
||||
|
|
@ -274,7 +266,6 @@ class vDDDTypes(object):
|
|||
"""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.
|
||||
|
||||
"""
|
||||
def __init__(self, dt):
|
||||
if type(dt) not in (datetime, date, timedelta, time):
|
||||
|
|
@ -287,7 +278,7 @@ class vDDDTypes(object):
|
|||
self.params = Parameters(dict(value='TIME'))
|
||||
|
||||
if (isinstance(dt, datetime) or isinstance(dt, time))\
|
||||
and getattr(dt, 'tzinfo', False):
|
||||
and getattr(dt, 'tzinfo', False):
|
||||
tzinfo = dt.tzinfo
|
||||
if tzinfo is not pytz.utc and not isinstance(tzinfo, tzutc):
|
||||
# set the timezone as a parameter to the property
|
||||
|
|
@ -327,7 +318,6 @@ class vDDDTypes(object):
|
|||
|
||||
class vDate(object):
|
||||
"""Render and generates iCalendar date format.
|
||||
|
||||
"""
|
||||
def __init__(self, dt):
|
||||
if not isinstance(dt, date):
|
||||
|
|
@ -362,7 +352,6 @@ class vDatetime(object):
|
|||
tzinfo component, if present. Otherwise an timezone-naive object is
|
||||
created. Be aware that there are certain limitations with timezone naive
|
||||
DATE-TIME components in the icalendar standard.
|
||||
|
||||
"""
|
||||
def __init__(self, dt):
|
||||
self.dt = dt
|
||||
|
|
@ -419,7 +408,6 @@ class vDatetime(object):
|
|||
class vDuration(object):
|
||||
"""Subclass of timedelta that renders itself in the iCalendar DURATION
|
||||
format.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, td):
|
||||
|
|
@ -474,7 +462,6 @@ class vDuration(object):
|
|||
|
||||
class vPeriod(object):
|
||||
"""A precise period of time.
|
||||
|
||||
"""
|
||||
def __init__(self, per):
|
||||
start, end_or_duration = per
|
||||
|
|
@ -547,7 +534,6 @@ class vPeriod(object):
|
|||
|
||||
class vWeekday(compat.unicode_type):
|
||||
"""This returns an unquoted weekday abbrevation.
|
||||
|
||||
"""
|
||||
week_days = CaselessDict({
|
||||
"SU": 0, "MO": 1, "TU": 2, "WE": 3, "TH": 4, "FR": 5, "SA": 6,
|
||||
|
|
@ -582,7 +568,6 @@ class vWeekday(compat.unicode_type):
|
|||
|
||||
class vFrequency(compat.unicode_type):
|
||||
"""A simple class that catches illegal values.
|
||||
|
||||
"""
|
||||
|
||||
frequencies = CaselessDict({
|
||||
|
|
@ -616,7 +601,6 @@ class vFrequency(compat.unicode_type):
|
|||
|
||||
class vRecur(CaselessDict):
|
||||
"""Recurrence definition.
|
||||
|
||||
"""
|
||||
|
||||
frequencies = ["SECONDLY", "MINUTELY", "HOURLY", "DAILY", "WEEKLY",
|
||||
|
|
@ -685,7 +669,6 @@ class vRecur(CaselessDict):
|
|||
|
||||
class vText(compat.unicode_type):
|
||||
"""Simple text.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
|
|
@ -709,7 +692,6 @@ class vText(compat.unicode_type):
|
|||
|
||||
class vTime(object):
|
||||
"""Render and generates iCalendar time format.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
|
|
@ -736,7 +718,6 @@ class vTime(object):
|
|||
|
||||
class vUri(compat.unicode_type):
|
||||
"""Uniform resource identifier is basically just an unquoted string.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
|
|
@ -758,7 +739,6 @@ class vUri(compat.unicode_type):
|
|||
|
||||
class vGeo(object):
|
||||
"""A special type that is only indirectly defined in the rfc.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, geo):
|
||||
|
|
@ -787,7 +767,6 @@ class vGeo(object):
|
|||
|
||||
class vUTCOffset(object):
|
||||
"""Renders itself as a utc offset.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, td):
|
||||
|
|
@ -798,9 +777,9 @@ class vUTCOffset(object):
|
|||
|
||||
def to_ical(self):
|
||||
|
||||
if self.td<timedelta(0):
|
||||
if self.td < timedelta(0):
|
||||
sign = '-%s'
|
||||
td = timedelta(0)-self.td # get timedelta relative to 0
|
||||
td = timedelta(0)-self.td # get timedelta relative to 0
|
||||
else:
|
||||
# Google Calendar rejects '0000' but accepts '+0000'
|
||||
sign = '+%s'
|
||||
|
|
@ -841,7 +820,6 @@ class vInline(compat.unicode_type):
|
|||
"""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.
|
||||
|
||||
"""
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
|
|
@ -863,7 +841,6 @@ class TypesFactory(CaselessDict):
|
|||
|
||||
The value and parameter names don't overlap. So one factory is enough for
|
||||
both kinds.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
import unittest
|
||||
if not hasattr(unittest.TestCase, 'assertIsNotNone'):
|
||||
import unittest2 as unittest
|
||||
unittest # pep 8
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//RDU Software//NONSGML HandCal//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:US-Eastern
|
||||
BEGIN:STANDARD
|
||||
DTSTART:19981025T020000
|
||||
RDATE:19981025T020000
|
||||
TZOFFSETFROM:-0400
|
||||
TZOFFSETTO:-0500
|
||||
TZNAME:EST
|
||||
END:STANDARD
|
||||
BEGIN:DAYLIGHT
|
||||
DTSTART:19990404T020000
|
||||
RDATE:19990404T020000
|
||||
TZOFFSETFROM:-0500
|
||||
TZOFFSETTO:-0400
|
||||
TZNAME:EDT
|
||||
END:DAYLIGHT
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:19980309T231000Z
|
||||
UID:guid-1.host1.com
|
||||
ORGANIZER;ROLE=CHAIR:MAILTO:mrbig@host.com
|
||||
ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CUTYPE=GROUP:
|
||||
MAILTO:employee-A@host.com
|
||||
DESCRIPTION:Project XYZ Review Meeting
|
||||
CATEGORIES:MEETING
|
||||
CLASS:PUBLIC
|
||||
CREATED:19980309T130000Z
|
||||
SUMMARY:XYZ Project Review
|
||||
DTSTART;TZID=US-Eastern:19980312T083000
|
||||
DTEND;TZID=US-Eastern:19980312T093000
|
||||
LOCATION:1CP Conference Room 4350
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
BEGIN:VCALENDAR
|
||||
METHOD:Request
|
||||
PRODID:-//My product//mxm.dk/
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
DESCRIPTION:This is a very long description that will be folded This is a
|
||||
very long description that will be folded This is a very long description
|
||||
that will be folded This is a very long description that will be folded Th
|
||||
is is a very long description that will be folded This is a very long desc
|
||||
ription that will be folded This is a very long description that will be f
|
||||
olded This is a very long description that will be folded This is a very l
|
||||
ong description that will be folded This is a very long description that w
|
||||
ill be folded
|
||||
PARTICIPANT;CN=Max M:MAILTO:maxm@mxm.dk
|
||||
DTEND:20050107T160000
|
||||
DTSTART:20050107T120000
|
||||
SUMMARY:A second event
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTEND:20050108T235900
|
||||
DTSTART:20050108T230000
|
||||
SUMMARY:A single event
|
||||
UID:42
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import icalendar
|
||||
import pytz
|
||||
import datetime
|
||||
import icalendar
|
||||
import os
|
||||
import pytz
|
||||
|
||||
|
||||
from . import unittest
|
||||
|
||||
class TestEncoding(unittest.TestCase):
|
||||
|
||||
def test_create_from_ical(self):
|
||||
|
|
@ -23,8 +22,10 @@ class TestEncoding(unittest.TestCase):
|
|||
event = cal.walk('VEVENT')[0]
|
||||
self.assertEqual(event['SUMMARY'].to_ical().decode('utf-8'),
|
||||
u'Non-ASCII Test: ÄÖÜ äöü €')
|
||||
self.assertEqual(event['DESCRIPTION'].to_ical().decode('utf-8'),
|
||||
u'icalendar should be able to handle non-ascii: €äüöÄÜÖ.')
|
||||
self.assertEqual(
|
||||
event['DESCRIPTION'].to_ical().decode('utf-8'),
|
||||
u'icalendar should be able to handle non-ascii: €äüöÄÜÖ.'
|
||||
)
|
||||
self.assertEqual(event['LOCATION'].to_ical().decode('utf-8'),
|
||||
u'Tribstrül')
|
||||
|
||||
|
|
@ -38,12 +39,24 @@ class TestEncoding(unittest.TestCase):
|
|||
cal.add('x-wr-relcalid', u"12345")
|
||||
|
||||
event = icalendar.Event()
|
||||
event.add('dtstart', datetime.datetime(2010, 10, 10, 10, 00, 00, tzinfo=pytz.utc))
|
||||
event.add('dtend', datetime.datetime(2010, 10, 10, 12, 00, 00, tzinfo=pytz.utc))
|
||||
event.add('created', datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=pytz.utc))
|
||||
event.add(
|
||||
'dtstart',
|
||||
datetime.datetime(2010, 10, 10, 10, 00, 00, tzinfo=pytz.utc)
|
||||
)
|
||||
event.add(
|
||||
'dtend',
|
||||
datetime.datetime(2010, 10, 10, 12, 00, 00, tzinfo=pytz.utc)
|
||||
)
|
||||
event.add(
|
||||
'created',
|
||||
datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=pytz.utc)
|
||||
)
|
||||
event.add('uid', u'123456')
|
||||
event.add('summary', u'Non-ASCII Test: ÄÖÜ äöü €')
|
||||
event.add('description', u'icalendar should be able to de/serialize non-ascii.')
|
||||
event.add(
|
||||
'description',
|
||||
u'icalendar should be able to de/serialize non-ascii.'
|
||||
)
|
||||
event.add('location', u'Tribstrül')
|
||||
cal.add_component(event)
|
||||
|
||||
|
|
@ -53,13 +66,15 @@ class TestEncoding(unittest.TestCase):
|
|||
|
||||
def test_create_event_simple(self):
|
||||
event = icalendar.Event()
|
||||
event.add("dtstart", datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=pytz.utc))
|
||||
event.add(
|
||||
"dtstart",
|
||||
datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=pytz.utc)
|
||||
)
|
||||
event.add("summary", u"åäö")
|
||||
out = event.to_ical()
|
||||
summary = b'SUMMARY:\xc3\xa5\xc3\xa4\xc3\xb6'
|
||||
self.assertTrue(summary in out.splitlines())
|
||||
|
||||
|
||||
def test_unicode_parameter_name(self):
|
||||
# Test for issue #80
|
||||
cal = icalendar.Calendar()
|
||||
|
|
@ -67,7 +82,9 @@ class TestEncoding(unittest.TestCase):
|
|||
event.add(u'DESCRIPTION', u'äöüßÄÖÜ')
|
||||
cal.add_component(event)
|
||||
c = cal.to_ical()
|
||||
self.assertEqual(c,
|
||||
b'BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDESCRIPTION:'\
|
||||
+ b'\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f\xc3\x84\xc3\x96\xc3\x9c\r\n'\
|
||||
+ b'END:VEVENT\r\nEND:VCALENDAR\r\n')
|
||||
self.assertEqual(
|
||||
c,
|
||||
b'BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDESCRIPTION:'
|
||||
+ b'\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f\xc3\x84\xc3\x96\xc3\x9c\r\n'
|
||||
+ b'END:VEVENT\r\nEND:VCALENDAR\r\n'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import pytz
|
|||
|
||||
class TestIssues(unittest.TestCase):
|
||||
|
||||
|
||||
def test_issue_53(self):
|
||||
"""Issue #53 - Parsing failure on some descriptions?
|
||||
https://github.com/collective/icalendar/issues/53
|
||||
|
|
@ -28,7 +27,6 @@ class TestIssues(unittest.TestCase):
|
|||
tz = timezones[0]
|
||||
self.assertEqual(tz['tzid'].to_ical(), b"America/New_York")
|
||||
|
||||
|
||||
def test_issue_55(self):
|
||||
"""Issue #55 - Parse error on utc-offset with seconds value
|
||||
https://github.com/collective/icalendar/issues/55
|
||||
|
|
@ -45,13 +43,15 @@ END:STANDARD
|
|||
END:VTIMEZONE"""
|
||||
|
||||
tz = icalendar.Timezone.from_ical(ical_str)
|
||||
self.assertEqual(tz.to_ical(),
|
||||
b'BEGIN:VTIMEZONE\r\nTZID:America/Los Angeles\r\nBEGIN:STANDARD\r\n'
|
||||
self.assertEqual(
|
||||
tz.to_ical(),
|
||||
b'BEGIN:VTIMEZONE\r\nTZID:America/Los Angeles\r\n'
|
||||
b'BEGIN:STANDARD\r\n'
|
||||
b'DTSTART:18831118T120702\r\nRDATE:18831118T120702\r\nTZNAME:PST'
|
||||
b'\r\nTZOFFSETFROM:-075258\r\nTZOFFSETTO:-0800\r\nEND:STANDARD\r\n'
|
||||
b'\r\nTZOFFSETFROM:-075258\r\nTZOFFSETTO:-0800\r\n'
|
||||
b'END:STANDARD\r\n'
|
||||
b'END:VTIMEZONE\r\n')
|
||||
|
||||
|
||||
def test_issue_58(self):
|
||||
"""Issue #58 - TZID on UTC DATE-TIMEs
|
||||
https://github.com/collective/icalendar/issues/58
|
||||
|
|
@ -64,11 +64,12 @@ END:VTIMEZONE"""
|
|||
event = icalendar.Event()
|
||||
dt = pytz.utc.localize(datetime.datetime(2012, 7, 16, 0, 0, 0))
|
||||
event.add('dtstart', dt)
|
||||
self.assertEqual(event.to_ical(),
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
b"BEGIN:VEVENT\r\n"
|
||||
b"DTSTART;VALUE=DATE-TIME:20120716T000000Z\r\n"
|
||||
b"END:VEVENT\r\n")
|
||||
|
||||
b"END:VEVENT\r\n"
|
||||
)
|
||||
|
||||
def test_issue_64(self):
|
||||
"""Issue #64 - Event.to_ical() fails for unicode strings
|
||||
|
|
@ -79,17 +80,21 @@ END:VTIMEZONE"""
|
|||
event = icalendar.Event()
|
||||
event.add("dtstart", datetime.datetime(2012, 9, 3, 0, 0, 0))
|
||||
event.add("summary", u"abcdef")
|
||||
self.assertEqual(event.to_ical(),
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:abcdef\r\nDTSTART;VALUE=DATE-TIME:"
|
||||
b"20120903T000000\r\nEND:VEVENT\r\n")
|
||||
b"20120903T000000\r\nEND:VEVENT\r\n"
|
||||
)
|
||||
|
||||
# Unicode characters
|
||||
event = icalendar.Event()
|
||||
event.add("dtstart", datetime.datetime(2012, 9, 3, 0, 0, 0))
|
||||
event.add("summary", u"åäö")
|
||||
self.assertEqual(event.to_ical(),
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:\xc3\xa5\xc3\xa4\xc3\xb6\r\n"
|
||||
b"DTSTART;VALUE=DATE-TIME:20120903T000000\r\nEND:VEVENT\r\n")
|
||||
b"DTSTART;VALUE=DATE-TIME:20120903T000000\r\nEND:VEVENT\r\n"
|
||||
)
|
||||
|
||||
def test_issue_70(self):
|
||||
"""Issue #70 - e.decode("RRULE") causes Attribute Error
|
||||
|
|
@ -111,10 +116,10 @@ END:VEVENT"""
|
|||
cal = icalendar.Calendar.from_ical(ical_str)
|
||||
recur = cal.decoded("RRULE")
|
||||
self.assertIsInstance(recur, icalendar.vRecur)
|
||||
self.assertEqual(recur.to_ical(),
|
||||
b'FREQ=WEEKLY;UNTIL=20070619T225959;INTERVAL=1')
|
||||
|
||||
|
||||
self.assertEqual(
|
||||
recur.to_ical(),
|
||||
b'FREQ=WEEKLY;UNTIL=20070619T225959;INTERVAL=1'
|
||||
)
|
||||
|
||||
def test_issue_82(self):
|
||||
"""Issue #82 - vBinary __repr__ called rather than to_ical from
|
||||
|
|
@ -127,12 +132,12 @@ END:VEVENT"""
|
|||
self.assertEqual(b.to_ical(), b'dGV4dA==')
|
||||
e = icalendar.Event()
|
||||
e.add('ATTACH', b)
|
||||
self.assertEqual(e.to_ical(),
|
||||
self.assertEqual(
|
||||
e.to_ical(),
|
||||
b"BEGIN:VEVENT\r\nATTACH;ENCODING=BASE64;FMTTYPE=text/plain;"
|
||||
b"VALUE=BINARY:dGV4dA==\r\nEND:VEVENT\r\n"
|
||||
)
|
||||
|
||||
|
||||
def test_issue_100(self):
|
||||
"""Issue #100 - Transformed doctests into unittests, Test fixes and
|
||||
cleanup.
|
||||
|
|
@ -142,7 +147,6 @@ END:VEVENT"""
|
|||
ical_content = "BEGIN:VEVENT\r\nSUMMARY;LANGUAGE=ru:te\r\nEND:VEVENT"
|
||||
icalendar.Event.from_ical(ical_content).to_ical()
|
||||
|
||||
|
||||
def test_issue_101(self):
|
||||
"""Issue #101 - icalender is choking on umlauts in ORGANIZER
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
from . import unittest
|
||||
from icalendar import Calendar
|
||||
import datetime
|
||||
import os
|
||||
|
||||
# An example from the RFC 2445 spec::
|
||||
|
||||
class TestGroupScheduled(unittest.TestCase):
|
||||
|
||||
def test_group_schedule(self):
|
||||
|
||||
directory = os.path.dirname(__file__)
|
||||
cal = Calendar.from_ical(
|
||||
open(os.path.join(directory, 'groupscheduled.ics'),'rb').read())
|
||||
|
||||
self.assertEqual(str(cal),
|
||||
"VCALENDAR({'VERSION': '2.0', 'PRODID': '-//RDU Software//NONSGML HandCal//EN'})")
|
||||
|
||||
timezones = cal.walk('VTIMEZONE')
|
||||
self.assertEqual(len(timezones), 1)
|
||||
|
||||
tz = timezones[0]
|
||||
self.assertEqual(str(tz), "VTIMEZONE({'TZID': 'US-Eastern'})")
|
||||
|
||||
std = tz.walk('STANDARD')[0]
|
||||
self.assertEqual(std.decoded('TZOFFSETFROM'), datetime.timedelta(-1, 72000))
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
# coding: utf-8
|
||||
import sys
|
||||
from . import unittest
|
||||
import doctest
|
||||
import os
|
||||
|
|
@ -261,22 +262,3 @@ class IcalendarTestCase (unittest.TestCase):
|
|||
from ..parser import q_join
|
||||
self.assertEqual(q_join(['Max', 'Moller', 'Rasmussen, Max']),
|
||||
'Max,Moller,"Rasmussen, Max"')
|
||||
|
||||
|
||||
def load_tests(loader=None, tests=None, pattern=None):
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(doctest.DocTestSuite(caselessdict))
|
||||
suite.addTest(doctest.DocTestSuite(parser))
|
||||
suite.addTest(doctest.DocTestSuite(prop))
|
||||
suite.addTest(doctest.DocTestSuite(cal))
|
||||
current_dir = os.path.dirname(__file__)
|
||||
for docfile in ['example.rst']:
|
||||
filename = os.path.abspath(os.path.join(current_dir, docfile))
|
||||
suite.addTest(
|
||||
doctest.DocFileSuite(
|
||||
docfile,
|
||||
optionflags=OPTIONFLAGS,
|
||||
globs={'__file__': filename}
|
||||
)
|
||||
)
|
||||
return suite
|
||||
|
|
|
|||
|
|
@ -3,14 +3,17 @@ from ..prop import vText
|
|||
from icalendar import Calendar
|
||||
import os
|
||||
|
||||
#A example with multiple VCALENDAR components::
|
||||
|
||||
class TestMultiple(unittest.TestCase):
|
||||
"""A example with multiple VCALENDAR components"""
|
||||
|
||||
def test_multiple(self):
|
||||
|
||||
directory = os.path.dirname(__file__)
|
||||
cals = Calendar.from_ical(
|
||||
open(os.path.join(directory, 'multiple.ics'),'rb').read(), multiple=True)
|
||||
open(os.path.join(directory, 'multiple.ics'), 'rb').read(),
|
||||
multiple=True
|
||||
)
|
||||
|
||||
self.assertEqual(len(cals), 2)
|
||||
self.assertSequenceEqual([comp.name for comp in cals[0].walk()],
|
||||
|
|
@ -18,5 +21,7 @@ class TestMultiple(unittest.TestCase):
|
|||
self.assertSequenceEqual([comp.name for comp in cals[1].walk()],
|
||||
['VCALENDAR', 'VEVENT', 'VEVENT'])
|
||||
|
||||
self.assertEqual(cals[0]['prodid'],
|
||||
vText('-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN'))
|
||||
self.assertEqual(
|
||||
cals[0]['prodid'],
|
||||
vText('-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN')
|
||||
)
|
||||
|
|
|
|||
|
|
@ -168,7 +168,6 @@ class TestPropertyParams(unittest.TestCase):
|
|||
'PARAMETER2': ['Value2', 'Value3']})
|
||||
)
|
||||
|
||||
|
||||
def test_parse_and_access_property_params(self):
|
||||
"""Parse an ics string and access some property parameters then.
|
||||
This is a follow-up of a question recieved per email.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
import icalendar
|
||||
import pytz
|
||||
import datetime
|
||||
import dateutil.parser
|
||||
import os
|
||||
|
||||
from . import unittest
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
from . import unittest
|
||||
from ..prop import vText
|
||||
from icalendar import Calendar
|
||||
import os
|
||||
|
||||
#A small example::
|
||||
class TestSmall(unittest.TestCase):
|
||||
|
||||
def test_small(self):
|
||||
|
||||
directory = os.path.dirname(__file__)
|
||||
cal = Calendar.from_ical(
|
||||
open(os.path.join(directory, 'small.ics'),'rb').read())
|
||||
self.assertEqual(str(cal), "VCALENDAR({'VERSION': '2.0', "
|
||||
"'METHOD': 'Request', "
|
||||
"'PRODID': '-//My product//mxm.dk/'})")
|
||||
|
||||
self.assertSequenceEqual([comp.name for comp in cal.walk()],
|
||||
['VCALENDAR', 'VEVENT', 'VEVENT'])
|
||||
|
||||
self.assertEqual(str(cal['prodid']), vText('-//My product//mxm.dk/'))
|
||||
|
||||
self.assertEqual(str(cal.decoded('prodid')), '-//My product//mxm.dk/')
|
||||
|
||||
first_event = cal.walk('vevent')[0]
|
||||
self.assertEqual(first_event['description'][:75],
|
||||
u'This is a very long description that will be folded This is a very long des')
|
||||
|
||||
self.assertEqual(first_event['summary'], vText('A second event'))
|
||||
|
|
@ -4,6 +4,7 @@ import os
|
|||
|
||||
from . import unittest
|
||||
|
||||
|
||||
class TestTime(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
|||
|
|
@ -12,9 +12,14 @@ class TestTimezoned(unittest.TestCase):
|
|||
|
||||
def test_create_from_ical(self):
|
||||
directory = os.path.dirname(__file__)
|
||||
cal = icalendar.Calendar.from_ical(open(os.path.join(directory, 'timezoned.ics'), 'rb').read())
|
||||
cal = icalendar.Calendar.from_ical(
|
||||
open(os.path.join(directory, 'timezoned.ics'), 'rb').read()
|
||||
)
|
||||
|
||||
self.assertEqual(cal['prodid'].to_ical(), b"-//Plone.org//NONSGML plone.app.event//EN")
|
||||
self.assertEqual(
|
||||
cal['prodid'].to_ical(),
|
||||
b"-//Plone.org//NONSGML plone.app.event//EN"
|
||||
)
|
||||
|
||||
timezones = cal.walk('VTIMEZONE')
|
||||
self.assertEqual(len(timezones), 1)
|
||||
|
|
@ -23,11 +28,19 @@ class TestTimezoned(unittest.TestCase):
|
|||
self.assertEqual(tz['tzid'].to_ical(), b"Europe/Vienna")
|
||||
|
||||
std = tz.walk('STANDARD')[0]
|
||||
self.assertEqual(std.decoded('TZOFFSETFROM'), datetime.timedelta(0, 7200))
|
||||
self.assertEqual(
|
||||
std.decoded('TZOFFSETFROM'),
|
||||
datetime.timedelta(0, 7200)
|
||||
)
|
||||
|
||||
ev1 = cal.walk('VEVENT')[0]
|
||||
self.assertEqual(ev1.decoded('DTSTART'), datetime.datetime(2012, 2, 13, 10, 0, 0, tzinfo=pytz.timezone('Europe/Vienna')))
|
||||
self.assertEqual(ev1.decoded('DTSTAMP'), datetime.datetime(2010, 10, 10, 9, 10, 10, tzinfo=pytz.utc))
|
||||
self.assertEqual(
|
||||
ev1.decoded('DTSTART'),
|
||||
datetime.datetime(2012, 2, 13, 10, 0, 0,
|
||||
tzinfo=pytz.timezone('Europe/Vienna')))
|
||||
self.assertEqual(
|
||||
ev1.decoded('DTSTAMP'),
|
||||
datetime.datetime(2010, 10, 10, 9, 10, 10, tzinfo=pytz.utc))
|
||||
|
||||
def test_create_to_ical(self):
|
||||
cal = icalendar.Calendar()
|
||||
|
|
@ -63,12 +76,22 @@ class TestTimezoned(unittest.TestCase):
|
|||
|
||||
event = icalendar.Event()
|
||||
tz = pytz.timezone("Europe/Vienna")
|
||||
event.add('dtstart', datetime.datetime(2012, 2, 13, 10, 00, 00, tzinfo=tz))
|
||||
event.add('dtend', datetime.datetime(2012, 2, 17, 18, 00, 00, tzinfo=tz))
|
||||
event.add('dtstamp', datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=tz))
|
||||
event.add('created', datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=tz))
|
||||
event.add(
|
||||
'dtstart',
|
||||
datetime.datetime(2012, 2, 13, 10, 00, 00, tzinfo=tz))
|
||||
event.add(
|
||||
'dtend',
|
||||
datetime.datetime(2012, 2, 17, 18, 00, 00, tzinfo=tz))
|
||||
event.add(
|
||||
'dtstamp',
|
||||
datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=tz))
|
||||
event.add(
|
||||
'created',
|
||||
datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=tz))
|
||||
event.add('uid', u'123456')
|
||||
event.add('last-modified', datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=tz))
|
||||
event.add(
|
||||
'last-modified',
|
||||
datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=tz))
|
||||
event.add('summary', u'artsprint 2012')
|
||||
# event.add('rrule', u'FREQ=YEARLY;INTERVAL=1;COUNT=10')
|
||||
event.add('description', u'sprinting at the artsprint')
|
||||
|
|
@ -85,13 +108,13 @@ class TestTimezoned(unittest.TestCase):
|
|||
test_out = b'|'.join(cal.to_ical().splitlines())
|
||||
test_out = test_out.decode('utf-8')
|
||||
|
||||
vtimezone_lines = "BEGIN:VTIMEZONE|TZID:Europe/Vienna|X-LIC-LOCATION:"\
|
||||
+ "Europe/Vienna|BEGIN:STANDARD|DTSTART;VALUE=DATE-TIME:19701025T03"\
|
||||
+ "0000|RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10|RRULE:FREQ=YEARLY;B"\
|
||||
+ "YDAY=-1SU;BYMONTH=3|TZNAME:CET|TZOFFSETFROM:+0200|TZOFFSETTO:+01"\
|
||||
+ "00|END:STANDARD|BEGIN:DAYLIGHT|DTSTART;VALUE=DATE-TIME:19700329T"\
|
||||
+ "020000|TZNAME:CEST|TZOFFSETFROM:+0100|TZOFFSETTO:+0200|END:DAYLI"\
|
||||
+ "GHT|END:VTIMEZONE"
|
||||
vtimezone_lines = "BEGIN:VTIMEZONE|TZID:Europe/Vienna|X-LIC-LOCATION:"
|
||||
"Europe/Vienna|BEGIN:STANDARD|DTSTART;VALUE=DATE-TIME:19701025T03"
|
||||
"0000|RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10|RRULE:FREQ=YEARLY;B"
|
||||
"YDAY=-1SU;BYMONTH=3|TZNAME:CET|TZOFFSETFROM:+0200|TZOFFSETTO:+01"
|
||||
"00|END:STANDARD|BEGIN:DAYLIGHT|DTSTART;VALUE=DATE-TIME:19700329T"
|
||||
"020000|TZNAME:CEST|TZOFFSETFROM:+0100|TZOFFSETTO:+0200|END:DAYLI"
|
||||
"GHT|END:VTIMEZONE"
|
||||
self.assertTrue(vtimezone_lines in test_out)
|
||||
|
||||
test_str = "DTSTART;TZID=Europe/Vienna;VALUE=DATE-TIME:20120213T100000"
|
||||
|
|
|
|||
|
|
@ -17,14 +17,18 @@ class TestCalComponent(unittest.TestCase):
|
|||
# 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/'
|
||||
self.assertEqual(c,
|
||||
Calendar({'PRODID': '-//max m//icalendar.mxm.dk/'}))
|
||||
self.assertEqual(
|
||||
c,
|
||||
Calendar({'PRODID': '-//max m//icalendar.mxm.dk/'})
|
||||
)
|
||||
|
||||
# or with a list
|
||||
c['ATTENDEE'] = ['Max M', 'Rasmussen']
|
||||
self.assertEqual(c,
|
||||
self.assertEqual(
|
||||
c,
|
||||
Calendar({'ATTENDEE': ['Max M', 'Rasmussen'],
|
||||
'PRODID': '-//max m//icalendar.mxm.dk/'}))
|
||||
'PRODID': '-//max m//icalendar.mxm.dk/'})
|
||||
)
|
||||
|
||||
# if you use the add method you don't have to considder if a value is
|
||||
# a list or not.
|
||||
|
|
@ -32,9 +36,11 @@ class TestCalComponent(unittest.TestCase):
|
|||
c.name = 'VEVENT'
|
||||
c.add('attendee', 'maxm@mxm.dk')
|
||||
c.add('attendee', 'test@example.dk')
|
||||
self.assertEqual(c,
|
||||
self.assertEqual(
|
||||
c,
|
||||
Event({'ATTENDEE': [prop.vCalAddress('maxm@mxm.dk'),
|
||||
prop.vCalAddress('test@example.dk')]}))
|
||||
prop.vCalAddress('test@example.dk')]})
|
||||
)
|
||||
|
||||
# You can get the values back directly ...
|
||||
c.add('prodid', '-//my product//')
|
||||
|
|
@ -53,8 +59,10 @@ class TestCalComponent(unittest.TestCase):
|
|||
c = Component()
|
||||
c.name = 'VCALENDAR'
|
||||
c.add('attendee', 'Max M')
|
||||
self.assertEqual(c.to_ical(),
|
||||
b'BEGIN:VCALENDAR\r\nATTENDEE:Max M\r\nEND:VCALENDAR\r\n')
|
||||
self.assertEqual(
|
||||
c.to_ical(),
|
||||
b'BEGIN:VCALENDAR\r\nATTENDEE:Max M\r\nEND:VCALENDAR\r\n'
|
||||
)
|
||||
|
||||
# Components can be nested, so You can add a subcompont. Eg a calendar
|
||||
# holds events.
|
||||
|
|
@ -62,15 +70,19 @@ class TestCalComponent(unittest.TestCase):
|
|||
e.name = 'VEVENT'
|
||||
e.add('dtend', '20000102T000000', encode=0)
|
||||
e.add('dtstart', '20000101T000000', encode=0)
|
||||
self.assertEqual(e.to_ical(),
|
||||
self.assertEqual(
|
||||
e.to_ical(),
|
||||
b'BEGIN:VEVENT\r\nDTEND:20000102T000000\r\n'
|
||||
+ b'DTSTART:20000101T000000\r\nSUMMARY:A brief history of time\r'
|
||||
+ b'\nEND:VEVENT\r\n')
|
||||
+ b'\nEND:VEVENT\r\n'
|
||||
)
|
||||
|
||||
c.add_component(e)
|
||||
self.assertEqual(c.subcomponents,
|
||||
self.assertEqual(
|
||||
c.subcomponents,
|
||||
[Event({'DTEND': '20000102T000000', 'DTSTART': '20000101T000000',
|
||||
'SUMMARY': 'A brief history of time'})])
|
||||
'SUMMARY': 'A brief history of time'})]
|
||||
)
|
||||
|
||||
# We can walk over nested componentes with the walk method.
|
||||
self.assertEqual([i.name for i in c.walk()], ['VCALENDAR', 'VEVENT'])
|
||||
|
|
@ -79,57 +91,75 @@ class TestCalComponent(unittest.TestCase):
|
|||
# them on their name.
|
||||
self.assertEqual([i.name for i in c.walk('VEVENT')], ['VEVENT'])
|
||||
|
||||
self.assertEqual([i['dtstart'] for i in c.walk('VEVENT')],
|
||||
['20000101T000000'])
|
||||
self.assertEqual(
|
||||
[i['dtstart'] for i in c.walk('VEVENT')],
|
||||
['20000101T000000']
|
||||
)
|
||||
|
||||
# We can enumerate property items recursively with the property_items
|
||||
# method.
|
||||
self.assertEqual(c.property_items(),
|
||||
self.assertEqual(
|
||||
c.property_items(),
|
||||
[('BEGIN', b'VCALENDAR'), ('ATTENDEE', prop.vCalAddress('Max M')),
|
||||
('BEGIN', b'VEVENT'), ('DTEND', '20000102T000000'),
|
||||
('DTSTART', '20000101T000000'),
|
||||
('SUMMARY', 'A brief history of time'), ('END', b'VEVENT'),
|
||||
('END', b'VCALENDAR')])
|
||||
('END', b'VCALENDAR')]
|
||||
)
|
||||
|
||||
# We can also enumerate property items just under the component.
|
||||
self.assertEqual(c.property_items(recursive=False),
|
||||
self.assertEqual(
|
||||
c.property_items(recursive=False),
|
||||
[('BEGIN', b'VCALENDAR'),
|
||||
('ATTENDEE', prop.vCalAddress('Max M')),
|
||||
('END', b'VCALENDAR')])
|
||||
('END', b'VCALENDAR')]
|
||||
)
|
||||
|
||||
sc = c.subcomponents[0]
|
||||
self.assertEqual(sc.property_items(recursive=False),
|
||||
self.assertEqual(
|
||||
sc.property_items(recursive=False),
|
||||
[('BEGIN', b'VEVENT'), ('DTEND', '20000102T000000'),
|
||||
('DTSTART', '20000101T000000'),
|
||||
('SUMMARY', 'A brief history of time'), ('END', b'VEVENT')])
|
||||
('SUMMARY', 'A brief history of time'), ('END', b'VEVENT')]
|
||||
)
|
||||
|
||||
# Text fields which span multiple mulitple lines require proper
|
||||
# indenting
|
||||
c = Calendar()
|
||||
c['description'] = u'Paragraph one\n\nParagraph two'
|
||||
self.assertEqual(c.to_ical(),
|
||||
b'BEGIN:VCALENDAR\r\nDESCRIPTION:Paragraph one\\n\\nParagraph two'
|
||||
+ b'\r\nEND:VCALENDAR\r\n')
|
||||
self.assertEqual(
|
||||
c.to_ical(),
|
||||
b'BEGIN:VCALENDAR\r\nDESCRIPTION:Paragraph one\\n\\nParagraph two'
|
||||
+ b'\r\nEND:VCALENDAR\r\n'
|
||||
)
|
||||
|
||||
# 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"'
|
||||
self.assertEqual(c,
|
||||
Calendar({'RESOURCES': 'Chair, Table, "Room: 42"'}))
|
||||
self.assertEqual(
|
||||
c,
|
||||
Calendar({'RESOURCES': 'Chair, Table, "Room: 42"'})
|
||||
)
|
||||
|
||||
self.assertEqual(c.to_ical(),
|
||||
self.assertEqual(
|
||||
c.to_ical(),
|
||||
b'BEGIN:VCALENDAR\r\nRESOURCES:Chair\\, Table\\, "Room: 42"\r\n'
|
||||
+ b'END:VCALENDAR\r\n')
|
||||
+ b'END:VCALENDAR\r\n'
|
||||
)
|
||||
|
||||
# The inline values must be handled by the get_inline() and
|
||||
# set_inline() methods.
|
||||
self.assertEqual(c.get_inline('resources', decode=0),
|
||||
[u'Chair', u'Table', u'Room: 42'])
|
||||
self.assertEqual(
|
||||
c.get_inline('resources', decode=0),
|
||||
[u'Chair', u'Table', u'Room: 42']
|
||||
)
|
||||
|
||||
# These can also be decoded
|
||||
self.assertEqual(c.get_inline('resources', decode=1),
|
||||
[b'Chair', b'Table', b'Room: 42'])
|
||||
self.assertEqual(
|
||||
c.get_inline('resources', decode=1),
|
||||
[b'Chair', b'Table', b'Room: 42']
|
||||
)
|
||||
|
||||
# You can set them directly ...
|
||||
c.set_inline('resources', ['A', 'List', 'of', 'some, recources'],
|
||||
|
|
@ -137,14 +167,18 @@ class TestCalComponent(unittest.TestCase):
|
|||
self.assertEqual(c['resources'], 'A,List,of,"some, recources"')
|
||||
|
||||
# ... and back again
|
||||
self.assertEqual(c.get_inline('resources', decode=0),
|
||||
['A', 'List', 'of', 'some, recources'])
|
||||
self.assertEqual(
|
||||
c.get_inline('resources', decode=0),
|
||||
['A', 'List', 'of', 'some, recources']
|
||||
)
|
||||
|
||||
c['freebusy'] = '19970308T160000Z/PT3H,19970308T200000Z/PT1H,'\
|
||||
+ '19970308T230000Z/19970309T000000Z'
|
||||
self.assertEqual(c.get_inline('freebusy', decode=0),
|
||||
self.assertEqual(
|
||||
c.get_inline('freebusy', decode=0),
|
||||
['19970308T160000Z/PT3H', '19970308T200000Z/PT1H',
|
||||
'19970308T230000Z/19970309T000000Z'])
|
||||
'19970308T230000Z/19970309T000000Z']
|
||||
)
|
||||
|
||||
freebusy = c.get_inline('freebusy', decode=1)
|
||||
self.assertTrue(isinstance(freebusy[0][0], datetime))
|
||||
|
|
@ -170,7 +204,8 @@ class TestCalComponent(unittest.TestCase):
|
|||
self.assertTrue(b"CREATED;VALUE=DATE-TIME:20101010T120000Z" in lines)
|
||||
self.assertTrue(b"DTSTAMP;VALUE=DATE-TIME:20101010T130000Z" in lines)
|
||||
self.assertTrue(
|
||||
b"LAST-MODIFIED;VALUE=DATE-TIME:20101010T160000Z" in lines)
|
||||
b"LAST-MODIFIED;VALUE=DATE-TIME:20101010T160000Z" in lines
|
||||
)
|
||||
|
||||
def test_cal_Component_add_no_reencode(self):
|
||||
"""Already encoded values should not be re-encoded.
|
||||
|
|
@ -216,8 +251,10 @@ class TestCal(unittest.TestCase):
|
|||
factory = ComponentFactory()
|
||||
component = factory['VEVENT']
|
||||
event = component(dtstart='19700101')
|
||||
self.assertEqual(event.to_ical(),
|
||||
b'BEGIN:VEVENT\r\nDTSTART:19700101\r\nEND:VEVENT\r\n')
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
b'BEGIN:VEVENT\r\nDTSTART:19700101\r\nEND:VEVENT\r\n'
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
factory.get('VCALENDAR', icalendar.cal.Component),
|
||||
|
|
@ -239,12 +276,13 @@ class TestCal(unittest.TestCase):
|
|||
cal.add_component(event)
|
||||
self.assertEqual(
|
||||
cal.subcomponents[0].to_ical(),
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Python meeting about calendaring\r\n'\
|
||||
+ b'DTSTART;VALUE=DATE-TIME:20050404T080000\r\nUID:42\r\n'\
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Python meeting about calendaring\r\n'
|
||||
+ b'DTSTART;VALUE=DATE-TIME:20050404T080000\r\nUID:42\r\n'
|
||||
+ b'END:VEVENT\r\n')
|
||||
|
||||
# Write to disc
|
||||
import tempfile, os
|
||||
import tempfile
|
||||
import os
|
||||
directory = tempfile.mkdtemp()
|
||||
open(os.path.join(directory, 'test.ics'), 'wb').write(cal.to_ical())
|
||||
|
||||
|
|
|
|||
|
|
@ -10,46 +10,60 @@ class TestCaselessdict(unittest.TestCase):
|
|||
keys = ['DTEND', 'DTSTAMP', 'DTSTART', 'UID', 'SUMMARY', 'LOCATION']
|
||||
|
||||
out = canonsort_keys(keys)
|
||||
self.assertEqual(out,
|
||||
['DTEND', 'DTSTAMP', 'DTSTART', 'LOCATION', 'SUMMARY', 'UID'])
|
||||
self.assertEqual(
|
||||
out,
|
||||
['DTEND', 'DTSTAMP', 'DTSTART', 'LOCATION', 'SUMMARY', 'UID']
|
||||
)
|
||||
|
||||
out = canonsort_keys(keys, ('SUMMARY', 'DTSTART', 'DTEND', ))
|
||||
self.assertEqual(out,
|
||||
['SUMMARY', 'DTSTART', 'DTEND', 'DTSTAMP', 'LOCATION', 'UID'])
|
||||
self.assertEqual(
|
||||
out,
|
||||
['SUMMARY', 'DTSTART', 'DTEND', 'DTSTAMP', 'LOCATION', 'UID']
|
||||
)
|
||||
|
||||
out = canonsort_keys(keys, ('UID', 'DTSTART', 'DTEND', ))
|
||||
self.assertEqual(out,
|
||||
['UID', 'DTSTART', 'DTEND', 'DTSTAMP', 'LOCATION', 'SUMMARY'])
|
||||
|
||||
out = canonsort_keys(keys,
|
||||
('UID', 'DTSTART', 'DTEND', 'RRULE', 'EXDATE'))
|
||||
self.assertEqual(out,
|
||||
['UID', 'DTSTART', 'DTEND', 'DTSTAMP', 'LOCATION', 'SUMMARY'])
|
||||
self.assertEqual(
|
||||
out,
|
||||
['UID', 'DTSTART', 'DTEND', 'DTSTAMP', 'LOCATION', 'SUMMARY']
|
||||
)
|
||||
|
||||
out = canonsort_keys(
|
||||
keys,
|
||||
('UID', 'DTSTART', 'DTEND', 'RRULE', 'EXDATE')
|
||||
)
|
||||
self.assertEqual(
|
||||
out,
|
||||
['UID', 'DTSTART', 'DTEND', 'DTSTAMP', 'LOCATION', 'SUMMARY']
|
||||
)
|
||||
|
||||
def test_caselessdict_canonsort_items(self):
|
||||
canonsort_items = icalendar.caselessdict.canonsort_items
|
||||
|
||||
d = dict(i=7, c='at', a=3.5, l=(2,3), e=[4,5], n=13, d={'x': 'y'},
|
||||
d = dict(i=7, c='at', a=3.5, l=(2, 3), e=[4, 5], n=13, d={'x': 'y'},
|
||||
r=1.0)
|
||||
|
||||
out = canonsort_items(d)
|
||||
self.assertEqual(out,
|
||||
self.assertEqual(
|
||||
out,
|
||||
[('a', 3.5), ('c', 'at'), ('d', {'x': 'y'}), ('e', [4, 5]),
|
||||
('i', 7), ('l', (2, 3)), ('n', 13), ('r', 1.0)])
|
||||
('i', 7), ('l', (2, 3)), ('n', 13), ('r', 1.0)]
|
||||
)
|
||||
|
||||
out = canonsort_items(d, ('i', 'c', 'a'))
|
||||
self.assertTrue(out,
|
||||
self.assertTrue(
|
||||
out,
|
||||
[('i', 7), ('c', 'at'), ('a', 3.5), ('d', {'x': 'y'}),
|
||||
('e', [4, 5]), ('l', (2, 3)), ('n', 13), ('r', 1.0)])
|
||||
|
||||
('e', [4, 5]), ('l', (2, 3)), ('n', 13), ('r', 1.0)]
|
||||
)
|
||||
|
||||
def test_CaselessDict(self):
|
||||
CaselessDict = icalendar.caselessdict.CaselessDict
|
||||
|
||||
ncd = CaselessDict(key1='val1', key2='val2')
|
||||
self.assertEqual(ncd,
|
||||
CaselessDict({'KEY2': 'val2', 'KEY1': 'val1'}))
|
||||
self.assertEqual(
|
||||
ncd,
|
||||
CaselessDict({'KEY2': 'val2', 'KEY1': 'val1'})
|
||||
)
|
||||
|
||||
self.assertEqual(ncd['key1'], 'val1')
|
||||
self.assertEqual(ncd['KEY1'], 'val1')
|
||||
|
|
@ -66,9 +80,9 @@ class TestCaselessdict(unittest.TestCase):
|
|||
self.assertTrue('key4' in ncd)
|
||||
|
||||
del ncd['key4']
|
||||
self.assertFalse(ncd.has_key('key4'))
|
||||
self.assertFalse('key4' in ncd)
|
||||
|
||||
ncd.update({'key5':'val5', 'KEY6':'val6', 'KEY5':'val7'})
|
||||
ncd.update({'key5': 'val5', 'KEY6': 'val6', 'KEY5': 'val7'})
|
||||
self.assertEqual(ncd['key6'], 'val6')
|
||||
|
||||
keys = list(ncd.keys())
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ class TestParserTools(unittest.TestCase):
|
|||
|
||||
def test_parser_tools_data_encode(self):
|
||||
|
||||
data1 = {u'k1': u'v1', 'k2': 'v2', u'k3': u'v3',
|
||||
'li1': ['it1', u'it2', {'k4': u'v4', u'k5': 'v5'}, 123]}
|
||||
data1 = {
|
||||
u'k1': u'v1', 'k2': 'v2', u'k3': u'v3',
|
||||
'li1': ['it1', u'it2', {'k4': u'v4', u'k5': 'v5'}, 123]
|
||||
}
|
||||
res = {b'k3': b'v3', b'k2': b'v2', b'k1': b'v1',
|
||||
b'li1': [b'it1', b'it2', {b'k5': b'v5', b'k4': b'v4'}, 123]}
|
||||
self.assertEqual(data_encode(data1), res)
|
||||
|
|
|
|||
|
|
@ -160,7 +160,10 @@ class TestProp(unittest.TestCase):
|
|||
|
||||
self.assertEqual(vDuration(timedelta(11)).to_ical(), b'P11D')
|
||||
self.assertEqual(vDuration(timedelta(-14)).to_ical(), b'-P14D')
|
||||
self.assertEqual(vDuration(timedelta(1, 7384)).to_ical(), b'P1DT2H3M4S')
|
||||
self.assertEqual(
|
||||
vDuration(timedelta(1, 7384)).to_ical(),
|
||||
b'P1DT2H3M4S'
|
||||
)
|
||||
self.assertEqual(vDuration(timedelta(1, 7380)).to_ical(), b'P1DT2H3M')
|
||||
self.assertEqual(vDuration(timedelta(1, 7200)).to_ical(), b'P1DT2H')
|
||||
self.assertEqual(vDuration(timedelta(0, 7200)).to_ical(), b'PT2H')
|
||||
|
|
@ -289,7 +292,10 @@ class TestProp(unittest.TestCase):
|
|||
r = vRecur.from_ical('FREQ=DAILY;INTERVAL=2;COUNT=10')
|
||||
self.assertEqual(r,
|
||||
{'COUNT': [10], 'FREQ': ['DAILY'], 'INTERVAL': [2]})
|
||||
self.assertEqual(vRecur(r).to_ical(), b'FREQ=DAILY;COUNT=10;INTERVAL=2')
|
||||
self.assertEqual(
|
||||
vRecur(r).to_ical(),
|
||||
b'FREQ=DAILY;COUNT=10;INTERVAL=2'
|
||||
)
|
||||
|
||||
r = vRecur.from_ical('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=-SU;'
|
||||
'BYHOUR=8,9;BYMINUTE=30')
|
||||
|
|
@ -301,7 +307,8 @@ class TestProp(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
vRecur(r).to_ical(),
|
||||
b'FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=-SU;BYMONTH=1'
|
||||
b'FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=-SU;'
|
||||
b'BYMONTH=1'
|
||||
)
|
||||
|
||||
# Some examples from the spec
|
||||
|
|
|
|||
2
tox.ini
2
tox.ini
|
|
@ -11,10 +11,8 @@ commands =
|
|||
coverage report --omit=*tests*
|
||||
coverage html --omit=*tests*
|
||||
|
||||
|
||||
[testenv:py33]
|
||||
basepython = python3.3
|
||||
deps =
|
||||
unittest2py3k
|
||||
coverage
|
||||
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue