vobject: add upstream tests
This commit is contained in:
parent
5cd43acb3c
commit
dc7ce824da
101
radicale_vobject/change_tz.py
Normal file
101
radicale_vobject/change_tz.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
"""Translate an ics file's events to a different timezone."""
|
||||||
|
|
||||||
|
from optparse import OptionParser
|
||||||
|
from radicale_vobject import icalendar, base
|
||||||
|
|
||||||
|
try:
|
||||||
|
import PyICU
|
||||||
|
except:
|
||||||
|
PyICU = None
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def change_tz(cal, new_timezone, default, utc_only=False, utc_tz=icalendar.utc):
|
||||||
|
"""
|
||||||
|
Change the timezone of the specified component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cal (Component): the component to change
|
||||||
|
new_timezone (tzinfo): the timezone to change to
|
||||||
|
default (tzinfo): a timezone to assume if the dtstart or dtend in cal
|
||||||
|
doesn't have an existing timezone
|
||||||
|
utc_only (bool): only convert dates that are in utc
|
||||||
|
utc_tz (tzinfo): the tzinfo to compare to for UTC when processing
|
||||||
|
utc_only=True
|
||||||
|
"""
|
||||||
|
|
||||||
|
for vevent in getattr(cal, 'vevent_list', []):
|
||||||
|
start = getattr(vevent, 'dtstart', None)
|
||||||
|
end = getattr(vevent, 'dtend', None)
|
||||||
|
for node in (start, end):
|
||||||
|
if node:
|
||||||
|
dt = node.value
|
||||||
|
if (isinstance(dt, datetime) and
|
||||||
|
(not utc_only or dt.tzinfo == utc_tz)):
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
dt = dt.replace(tzinfo=default)
|
||||||
|
node.value = dt.astimezone(new_timezone)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
options, args = get_options()
|
||||||
|
if PyICU is None:
|
||||||
|
print("Failure. change_tz requires PyICU, exiting")
|
||||||
|
elif options.list:
|
||||||
|
for tz_string in PyICU.TimeZone.createEnumeration():
|
||||||
|
print(tz_string)
|
||||||
|
elif args:
|
||||||
|
utc_only = options.utc
|
||||||
|
if utc_only:
|
||||||
|
which = "only UTC"
|
||||||
|
else:
|
||||||
|
which = "all"
|
||||||
|
print("Converting {0!s} events".format(which))
|
||||||
|
ics_file = args[0]
|
||||||
|
if len(args) > 1:
|
||||||
|
timezone = PyICU.ICUtzinfo.getInstance(args[1])
|
||||||
|
else:
|
||||||
|
timezone = PyICU.ICUtzinfo.default
|
||||||
|
print("... Reading {0!s}".format(ics_file))
|
||||||
|
cal = base.readOne(open(ics_file))
|
||||||
|
change_tz(cal, timezone, PyICU.ICUtzinfo.default, utc_only)
|
||||||
|
|
||||||
|
out_name = ics_file + '.converted'
|
||||||
|
print("... Writing {0!s}".format(out_name))
|
||||||
|
|
||||||
|
with open(out_name, 'wb') as out:
|
||||||
|
cal.serialize(out)
|
||||||
|
|
||||||
|
print("Done")
|
||||||
|
|
||||||
|
|
||||||
|
version = "0.1"
|
||||||
|
|
||||||
|
|
||||||
|
def get_options():
|
||||||
|
# Configuration options
|
||||||
|
|
||||||
|
usage = """usage: %prog [options] ics_file [timezone]"""
|
||||||
|
parser = OptionParser(usage=usage, version=version)
|
||||||
|
parser.set_description("change_tz will convert the timezones in an ics file. ")
|
||||||
|
|
||||||
|
parser.add_option("-u", "--only-utc", dest="utc", action="store_true",
|
||||||
|
default=False, help="Only change UTC events.")
|
||||||
|
parser.add_option("-l", "--list", dest="list", action="store_true",
|
||||||
|
default=False, help="List available timezones")
|
||||||
|
|
||||||
|
(cmdline_options, args) = parser.parse_args()
|
||||||
|
if not args and not cmdline_options.list:
|
||||||
|
print("error: too few arguments given")
|
||||||
|
print
|
||||||
|
print(parser.format_help())
|
||||||
|
return False, False
|
||||||
|
|
||||||
|
return cmdline_options, args
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Aborted")
|
951
radicale_vobject/tests/test_base.py
Normal file
951
radicale_vobject/tests/test_base.py
Normal file
@ -0,0 +1,951 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import dateutil
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from dateutil.tz import tzutc
|
||||||
|
from dateutil.rrule import rrule, rruleset, WEEKLY, MONTHLY
|
||||||
|
|
||||||
|
from radicale_vobject import base, iCalendar
|
||||||
|
from radicale_vobject import icalendar
|
||||||
|
|
||||||
|
from radicale_vobject.base import __behaviorRegistry as behavior_registry
|
||||||
|
from radicale_vobject.base import ContentLine, parseLine, ParseError
|
||||||
|
from radicale_vobject.base import readComponents, textLineToContentLine
|
||||||
|
|
||||||
|
from radicale_vobject.change_tz import change_tz
|
||||||
|
|
||||||
|
from radicale_vobject.icalendar import MultiDateBehavior, PeriodBehavior, \
|
||||||
|
RecurringComponent, utc
|
||||||
|
from radicale_vobject.icalendar import parseDtstart, stringToTextValues, \
|
||||||
|
stringToPeriod, timedeltaToString
|
||||||
|
|
||||||
|
two_hours = datetime.timedelta(hours=2)
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_filepath(path):
|
||||||
|
"""
|
||||||
|
Helper function to get the filepath of test files.
|
||||||
|
"""
|
||||||
|
return os.path.join(os.path.dirname(__file__), "test_files", path)
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_file(path):
|
||||||
|
"""
|
||||||
|
Helper function to open and read test files.
|
||||||
|
"""
|
||||||
|
filepath = get_test_filepath(path)
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
|
# On python 2, this library operates on bytes.
|
||||||
|
f = open(filepath, 'r')
|
||||||
|
else:
|
||||||
|
# On python 3, it operates on unicode. We need to specify an encoding
|
||||||
|
# for systems for which the preferred encoding isn't utf-8 (e.g windows)
|
||||||
|
f = open(filepath, 'r', encoding='utf-8')
|
||||||
|
text = f.read()
|
||||||
|
f.close()
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
class TestCalendarSerializing(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Test creating an iCalendar file
|
||||||
|
"""
|
||||||
|
max_diff = None
|
||||||
|
|
||||||
|
def test_scratchbuild(self):
|
||||||
|
"""
|
||||||
|
CreateCalendar 2.0 format from scratch
|
||||||
|
"""
|
||||||
|
test_cal = get_test_file("simple_2_0_test.ics")
|
||||||
|
cal = base.newFromBehavior('vcalendar', '2.0')
|
||||||
|
cal.add('vevent')
|
||||||
|
cal.vevent.add('dtstart').value = datetime.datetime(2006, 5, 9)
|
||||||
|
cal.vevent.add('description').value = "Test event"
|
||||||
|
cal.vevent.add('created').value = \
|
||||||
|
datetime.datetime(2006, 1, 1, 10,
|
||||||
|
tzinfo=dateutil.tz.tzical(
|
||||||
|
get_test_filepath("timezones.ics")).get('US/Pacific'))
|
||||||
|
cal.vevent.add('uid').value = "Not very random UID"
|
||||||
|
cal.vevent.add('dtstamp').value = datetime.datetime(2017, 6, 26, 0, tzinfo=tzutc())
|
||||||
|
|
||||||
|
# Note we're normalizing line endings, because no one got time for that.
|
||||||
|
self.assertEqual(
|
||||||
|
cal.serialize().replace('\r\n', '\n'),
|
||||||
|
test_cal.replace('\r\n', '\n')
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_unicode(self):
|
||||||
|
"""
|
||||||
|
Test unicode characters
|
||||||
|
"""
|
||||||
|
test_cal = get_test_file("utf8_test.ics")
|
||||||
|
vevent = base.readOne(test_cal).vevent
|
||||||
|
vevent2 = base.readOne(vevent.serialize())
|
||||||
|
self.assertEqual(str(vevent), str(vevent2))
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
vevent.summary.value,
|
||||||
|
'The title こんにちはキティ'
|
||||||
|
)
|
||||||
|
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
|
test_cal = test_cal.decode('utf-8')
|
||||||
|
vevent = base.readOne(test_cal).vevent
|
||||||
|
vevent2 = base.readOne(vevent.serialize())
|
||||||
|
self.assertEqual(str(vevent), str(vevent2))
|
||||||
|
self.assertEqual(
|
||||||
|
vevent.summary.value,
|
||||||
|
u'The title こんにちはキティ'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_wrapping(self):
|
||||||
|
"""
|
||||||
|
Should support input file with a long text field covering multiple lines
|
||||||
|
"""
|
||||||
|
test_journal = get_test_file("journal.ics")
|
||||||
|
vobj = base.readOne(test_journal)
|
||||||
|
vjournal = base.readOne(vobj.serialize())
|
||||||
|
self.assertTrue('Joe, Lisa, and Bob' in vjournal.description.value)
|
||||||
|
self.assertTrue('Tuesday.\n2.' in vjournal.description.value)
|
||||||
|
|
||||||
|
def test_multiline(self):
|
||||||
|
"""
|
||||||
|
Multi-text serialization test
|
||||||
|
"""
|
||||||
|
category = base.newFromBehavior('categories')
|
||||||
|
category.value = ['Random category']
|
||||||
|
self.assertEqual(
|
||||||
|
category.serialize().strip(),
|
||||||
|
"CATEGORIES:Random category"
|
||||||
|
)
|
||||||
|
|
||||||
|
category.value.append('Other category')
|
||||||
|
self.assertEqual(
|
||||||
|
category.serialize().strip(),
|
||||||
|
"CATEGORIES:Random category,Other category"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_semicolon_separated(self):
|
||||||
|
"""
|
||||||
|
Semi-colon separated multi-text serialization test
|
||||||
|
"""
|
||||||
|
request_status = base.newFromBehavior('request-status')
|
||||||
|
request_status.value = ['5.1', 'Service unavailable']
|
||||||
|
self.assertEqual(
|
||||||
|
request_status.serialize().strip(),
|
||||||
|
"REQUEST-STATUS:5.1;Service unavailable"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def test_unicode_multiline():
|
||||||
|
"""
|
||||||
|
Test multiline unicode characters
|
||||||
|
"""
|
||||||
|
cal = iCalendar()
|
||||||
|
cal.add('method').value = 'REQUEST'
|
||||||
|
cal.add('vevent')
|
||||||
|
cal.vevent.add('created').value = datetime.datetime.now()
|
||||||
|
cal.vevent.add('summary').value = 'Классное событие'
|
||||||
|
cal.vevent.add('description').value = ('Классное событие Классное событие Классное событие Классное событие '
|
||||||
|
'Классное событие Классsdssdное событие')
|
||||||
|
|
||||||
|
# json tries to encode as utf-8 and it would break if some chars could not be encoded
|
||||||
|
json.dumps(cal.serialize())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def test_ical_to_hcal():
|
||||||
|
"""
|
||||||
|
Serializing iCalendar to hCalendar.
|
||||||
|
|
||||||
|
Since Hcalendar is experimental and the behavior doesn't seem to want to load,
|
||||||
|
This test will have to wait.
|
||||||
|
|
||||||
|
|
||||||
|
tzs = dateutil.tz.tzical(get_test_filepath("timezones.ics"))
|
||||||
|
cal = base.newFromBehavior('hcalendar')
|
||||||
|
self.assertEqual(
|
||||||
|
str(cal.behavior),
|
||||||
|
"<class 'radicale_vobject.hcalendar.HCalendar'>"
|
||||||
|
)
|
||||||
|
cal.add('vevent')
|
||||||
|
cal.vevent.add('summary').value = "this is a note"
|
||||||
|
cal.vevent.add('url').value = "http://microformats.org/code/hcalendar/creator"
|
||||||
|
cal.vevent.add('dtstart').value = datetime.date(2006,2,27)
|
||||||
|
cal.vevent.add('location').value = "a place"
|
||||||
|
cal.vevent.add('dtend').value = datetime.date(2006,2,27) + datetime.timedelta(days = 2)
|
||||||
|
|
||||||
|
event2 = cal.add('vevent')
|
||||||
|
event2.add('summary').value = "Another one"
|
||||||
|
event2.add('description').value = "The greatest thing ever!"
|
||||||
|
event2.add('dtstart').value = datetime.datetime(1998, 12, 17, 16, 42, tzinfo = tzs.get('US/Pacific'))
|
||||||
|
event2.add('location').value = "somewhere else"
|
||||||
|
event2.add('dtend').value = event2.dtstart.value + datetime.timedelta(days = 6)
|
||||||
|
hcal = cal.serialize()
|
||||||
|
"""
|
||||||
|
#self.assertEqual(
|
||||||
|
# str(hcal),
|
||||||
|
# """<span class="vevent">
|
||||||
|
# <a class="url" href="http://microformats.org/code/hcalendar/creator">
|
||||||
|
# <span class="summary">this is a note</span>:
|
||||||
|
# <abbr class="dtstart", title="20060227">Monday, February 27</abbr>
|
||||||
|
# - <abbr class="dtend", title="20060301">Tuesday, February 28</abbr>
|
||||||
|
# at <span class="location">a place</span>
|
||||||
|
# </a>
|
||||||
|
# </span>
|
||||||
|
# <span class="vevent">
|
||||||
|
# <span class="summary">Another one</span>:
|
||||||
|
# <abbr class="dtstart", title="19981217T164200-0800">Thursday, December 17, 16:42</abbr>
|
||||||
|
# - <abbr class="dtend", title="19981223T164200-0800">Wednesday, December 23, 16:42</abbr>
|
||||||
|
# at <span class="location">somewhere else</span>
|
||||||
|
# <div class="description">The greatest thing ever!</div>
|
||||||
|
# </span>
|
||||||
|
# """
|
||||||
|
#)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBehaviors(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Test Behaviors
|
||||||
|
"""
|
||||||
|
def test_general_behavior(self):
|
||||||
|
"""
|
||||||
|
Tests for behavior registry, getting and creating a behavior.
|
||||||
|
"""
|
||||||
|
# Check expected behavior registry.
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(behavior_registry.keys()),
|
||||||
|
['', 'ACTION', 'ADR', 'AVAILABLE', 'BUSYTYPE', 'CALSCALE',
|
||||||
|
'CATEGORIES', 'CLASS', 'COMMENT', 'COMPLETED', 'CONTACT',
|
||||||
|
'CREATED', 'DAYLIGHT', 'DESCRIPTION', 'DTEND', 'DTSTAMP',
|
||||||
|
'DTSTART', 'DUE', 'DURATION', 'EXDATE', 'EXRULE', 'FN', 'FREEBUSY',
|
||||||
|
'LABEL', 'LAST-MODIFIED', 'LOCATION', 'METHOD', 'N', 'ORG',
|
||||||
|
'PHOTO', 'PRODID', 'RDATE', 'RECURRENCE-ID', 'RELATED-TO',
|
||||||
|
'REQUEST-STATUS', 'RESOURCES', 'REV', 'RRULE', 'STANDARD', 'STATUS',
|
||||||
|
'SUMMARY', 'TRANSP', 'TRIGGER', 'UID', 'VALARM', 'VAVAILABILITY',
|
||||||
|
'VCALENDAR', 'VCARD', 'VEVENT', 'VFREEBUSY', 'VJOURNAL',
|
||||||
|
'VTIMEZONE', 'VTODO']
|
||||||
|
)
|
||||||
|
|
||||||
|
# test get_behavior
|
||||||
|
behavior = base.getBehavior('VCALENDAR')
|
||||||
|
self.assertEqual(
|
||||||
|
str(behavior),
|
||||||
|
"<class 'radicale_vobject.icalendar.VCalendar2_0'>"
|
||||||
|
)
|
||||||
|
self.assertTrue(behavior.isComponent)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
base.getBehavior("invalid_name"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
# test for ContentLine (not a component)
|
||||||
|
non_component_behavior = base.getBehavior('RDATE')
|
||||||
|
self.assertFalse(non_component_behavior.isComponent)
|
||||||
|
|
||||||
|
def test_MultiDateBehavior(self):
|
||||||
|
"""
|
||||||
|
Test MultiDateBehavior
|
||||||
|
"""
|
||||||
|
parseRDate = MultiDateBehavior.transformToNative
|
||||||
|
self.assertEqual(
|
||||||
|
str(parseRDate(textLineToContentLine("RDATE;VALUE=DATE:19970304,19970504,19970704,19970904"))),
|
||||||
|
"<RDATE{'VALUE': ['DATE']}[datetime.date(1997, 3, 4), datetime.date(1997, 5, 4), datetime.date(1997, 7, 4), datetime.date(1997, 9, 4)]>"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
str(parseRDate(textLineToContentLine("RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z,19960404T010000Z/PT3H"))),
|
||||||
|
"<RDATE{'VALUE': ['PERIOD']}[(datetime.datetime(1996, 4, 3, 2, 0, tzinfo=tzutc()), datetime.datetime(1996, 4, 3, 4, 0, tzinfo=tzutc())), (datetime.datetime(1996, 4, 4, 1, 0, tzinfo=tzutc()), datetime.timedelta(0, 10800))]>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_periodBehavior(self):
|
||||||
|
"""
|
||||||
|
Test PeriodBehavior
|
||||||
|
"""
|
||||||
|
line = ContentLine('test', [], '', isNative=True)
|
||||||
|
line.behavior = PeriodBehavior
|
||||||
|
line.value = [(datetime.datetime(2006, 2, 16, 10), two_hours)]
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
line.transformFromNative().value,
|
||||||
|
'20060216T100000/PT2H'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
line.transformToNative().value,
|
||||||
|
[(datetime.datetime(2006, 2, 16, 10, 0),
|
||||||
|
datetime.timedelta(0, 7200))]
|
||||||
|
)
|
||||||
|
|
||||||
|
line.value.append((datetime.datetime(2006, 5, 16, 10), two_hours))
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
line.serialize().strip(),
|
||||||
|
'TEST:20060216T100000/PT2H,20060516T100000/PT2H'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestVTodo(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
VTodo Tests
|
||||||
|
"""
|
||||||
|
def test_vtodo(self):
|
||||||
|
"""
|
||||||
|
Test VTodo
|
||||||
|
"""
|
||||||
|
vtodo = get_test_file("vtodo.ics")
|
||||||
|
obj = base.readOne(vtodo)
|
||||||
|
obj.vtodo.add('completed')
|
||||||
|
obj.vtodo.completed.value = datetime.datetime(2015,5,5,13,30)
|
||||||
|
self.assertEqual(obj.vtodo.completed.serialize()[0:23],
|
||||||
|
'COMPLETED:20150505T1330')
|
||||||
|
obj = base.readOne(obj.serialize())
|
||||||
|
self.assertEqual(obj.vtodo.completed.value,
|
||||||
|
datetime.datetime(2015,5,5,13,30))
|
||||||
|
|
||||||
|
|
||||||
|
class TestVobject(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
VObject Tests
|
||||||
|
"""
|
||||||
|
max_diff = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
"""
|
||||||
|
Method for setting up class fixture before running tests in the class.
|
||||||
|
Fetches test file.
|
||||||
|
"""
|
||||||
|
cls.simple_test_cal = get_test_file("simple_test.ics")
|
||||||
|
|
||||||
|
def test_readComponents(self):
|
||||||
|
"""
|
||||||
|
Test if reading components correctly
|
||||||
|
"""
|
||||||
|
cal = next(readComponents(self.simple_test_cal))
|
||||||
|
|
||||||
|
self.assertEqual(str(cal), "<VCALENDAR| [<VEVENT| [<SUMMARY{'BLAH': ['hi!']}Bastille Day Party>]>]>")
|
||||||
|
self.assertEqual(str(cal.vevent.summary), "<SUMMARY{'BLAH': ['hi!']}Bastille Day Party>")
|
||||||
|
|
||||||
|
def test_parseLine(self):
|
||||||
|
"""
|
||||||
|
Test line parsing
|
||||||
|
"""
|
||||||
|
self.assertEqual(parseLine("BLAH:"), ('BLAH', [], '', None))
|
||||||
|
self.assertEqual(
|
||||||
|
parseLine("RDATE:VALUE=DATE:19970304,19970504,19970704,19970904"),
|
||||||
|
('RDATE', [], 'VALUE=DATE:19970304,19970504,19970704,19970904', None)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
parseLine('DESCRIPTION;ALTREP="http://www.wiz.org":The Fall 98 Wild Wizards Conference - - Las Vegas, NV, USA'),
|
||||||
|
('DESCRIPTION', [['ALTREP', 'http://www.wiz.org']], 'The Fall 98 Wild Wizards Conference - - Las Vegas, NV, USA', None)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
parseLine("EMAIL;PREF;INTERNET:john@nowhere.com"),
|
||||||
|
('EMAIL', [['PREF'], ['INTERNET']], 'john@nowhere.com', None)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
parseLine('EMAIL;TYPE="blah",hah;INTERNET="DIGI",DERIDOO:john@nowhere.com'),
|
||||||
|
('EMAIL', [['TYPE', 'blah', 'hah'], ['INTERNET', 'DIGI', 'DERIDOO']], 'john@nowhere.com', None)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
parseLine('item1.ADR;type=HOME;type=pref:;;Reeperbahn 116;Hamburg;;20359;'),
|
||||||
|
('ADR', [['type', 'HOME'], ['type', 'pref']], ';;Reeperbahn 116;Hamburg;;20359;', 'item1')
|
||||||
|
)
|
||||||
|
self.assertRaises(ParseError, parseLine, ":")
|
||||||
|
|
||||||
|
|
||||||
|
class TestGeneralFileParsing(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
General tests for parsing ics files.
|
||||||
|
"""
|
||||||
|
def test_readOne(self):
|
||||||
|
"""
|
||||||
|
Test reading first component of ics
|
||||||
|
"""
|
||||||
|
cal = get_test_file("silly_test.ics")
|
||||||
|
silly = base.readOne(cal)
|
||||||
|
self.assertEqual(
|
||||||
|
str(silly),
|
||||||
|
"<SILLYPROFILE| [<MORESTUFF{}this line is not folded, but in practice probably ought to be, as it is exceptionally long, and moreover demonstratively stupid>, <SILLYNAME{}name>, <STUFF{}foldedline>]>"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
str(silly.stuff),
|
||||||
|
"<STUFF{}foldedline>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_importing(self):
|
||||||
|
"""
|
||||||
|
Test importing ics
|
||||||
|
"""
|
||||||
|
cal = get_test_file("standard_test.ics")
|
||||||
|
c = base.readOne(cal, validate=True)
|
||||||
|
self.assertEqual(
|
||||||
|
str(c.vevent.valarm.trigger),
|
||||||
|
"<TRIGGER{}-1 day, 0:00:00>"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
str(c.vevent.dtstart.value),
|
||||||
|
"2002-10-28 14:00:00-08:00"
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
isinstance(c.vevent.dtstart.value, datetime.datetime)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
str(c.vevent.dtend.value),
|
||||||
|
"2002-10-28 15:00:00-08:00"
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
isinstance(c.vevent.dtend.value, datetime.datetime)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
c.vevent.dtstamp.value,
|
||||||
|
datetime.datetime(2002, 10, 28, 1, 17, 6, tzinfo=tzutc())
|
||||||
|
)
|
||||||
|
|
||||||
|
vevent = c.vevent.transformFromNative()
|
||||||
|
self.assertEqual(
|
||||||
|
str(vevent.rrule),
|
||||||
|
"<RRULE{}FREQ=Weekly;COUNT=10>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_bad_stream(self):
|
||||||
|
"""
|
||||||
|
Test bad ics stream
|
||||||
|
"""
|
||||||
|
cal = get_test_file("badstream.ics")
|
||||||
|
self.assertRaises(ParseError, base.readOne, cal)
|
||||||
|
|
||||||
|
def test_bad_line(self):
|
||||||
|
"""
|
||||||
|
Test bad line in ics file
|
||||||
|
"""
|
||||||
|
cal = get_test_file("badline.ics")
|
||||||
|
self.assertRaises(ParseError, base.readOne, cal)
|
||||||
|
|
||||||
|
newcal = base.readOne(cal, ignoreUnreadable=True)
|
||||||
|
self.assertEqual(
|
||||||
|
str(newcal.vevent.x_bad_underscore),
|
||||||
|
'<X-BAD-UNDERSCORE{}TRUE>'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_parseParams(self):
|
||||||
|
"""
|
||||||
|
Test parsing parameters
|
||||||
|
"""
|
||||||
|
self.assertEqual(
|
||||||
|
base.parseParams(';ALTREP="http://www.wiz.org"'),
|
||||||
|
[['ALTREP', 'http://www.wiz.org']]
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
base.parseParams(';ALTREP="http://www.wiz.org;;",Blah,Foo;NEXT=Nope;BAR'),
|
||||||
|
[['ALTREP', 'http://www.wiz.org;;', 'Blah', 'Foo'],
|
||||||
|
['NEXT', 'Nope'], ['BAR']]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestVcards(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Test VCards
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
"""
|
||||||
|
Method for setting up class fixture before running tests in the class.
|
||||||
|
Fetches test file.
|
||||||
|
"""
|
||||||
|
cls.test_file = get_test_file("vcard_with_groups.ics")
|
||||||
|
cls.card = base.readOne(cls.test_file)
|
||||||
|
|
||||||
|
def test_vcard_creation(self):
|
||||||
|
"""
|
||||||
|
Test creating a vCard
|
||||||
|
"""
|
||||||
|
vcard = base.newFromBehavior('vcard', '3.0')
|
||||||
|
self.assertEqual(
|
||||||
|
str(vcard),
|
||||||
|
"<VCARD| []>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_default_behavior(self):
|
||||||
|
"""
|
||||||
|
Default behavior test.
|
||||||
|
"""
|
||||||
|
card = self.card
|
||||||
|
self.assertEqual(
|
||||||
|
base.getBehavior('note'),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
str(card.note.value),
|
||||||
|
"The Mayor of the great city of Goerlitz in the great country of Germany.\nNext line."
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_groups(self):
|
||||||
|
"""
|
||||||
|
vCard groups test
|
||||||
|
"""
|
||||||
|
card = self.card
|
||||||
|
self.assertEqual(
|
||||||
|
str(card.group),
|
||||||
|
'home'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
str(card.tel.group),
|
||||||
|
'home'
|
||||||
|
)
|
||||||
|
|
||||||
|
card.group = card.tel.group = 'new'
|
||||||
|
self.assertEqual(
|
||||||
|
str(card.tel.serialize().strip()),
|
||||||
|
'new.TEL;TYPE=fax,voice,msg:+49 3581 123456'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
str(card.serialize().splitlines()[0]),
|
||||||
|
'new.BEGIN:VCARD'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_vcard_3_parsing(self):
|
||||||
|
"""
|
||||||
|
VCARD 3.0 parse test
|
||||||
|
"""
|
||||||
|
test_file = get_test_file("simple_3_0_test.ics")
|
||||||
|
card = base.readOne(test_file)
|
||||||
|
# value not rendering correctly?
|
||||||
|
#self.assertEqual(
|
||||||
|
# card.adr.value,
|
||||||
|
# "<Address: Haight Street 512;\nEscape, Test\nNovosibirsk, 80214\nGnuland>"
|
||||||
|
#)
|
||||||
|
self.assertEqual(
|
||||||
|
card.org.value,
|
||||||
|
["University of Novosibirsk", "Department of Octopus Parthenogenesis"]
|
||||||
|
)
|
||||||
|
|
||||||
|
for _ in range(3):
|
||||||
|
new_card = base.readOne(card.serialize())
|
||||||
|
self.assertEqual(new_card.org.value, card.org.value)
|
||||||
|
card = new_card
|
||||||
|
|
||||||
|
|
||||||
|
class TestIcalendar(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Tests for icalendar.py
|
||||||
|
"""
|
||||||
|
max_diff = None
|
||||||
|
def test_parseDTStart(self):
|
||||||
|
"""
|
||||||
|
Should take a content line and return a datetime object.
|
||||||
|
"""
|
||||||
|
self.assertEqual(
|
||||||
|
parseDtstart(textLineToContentLine("DTSTART:20060509T000000")),
|
||||||
|
datetime.datetime(2006, 5, 9, 0, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_regexes(self):
|
||||||
|
"""
|
||||||
|
Test regex patterns
|
||||||
|
"""
|
||||||
|
self.assertEqual(
|
||||||
|
re.findall(base.patterns['name'], '12foo-bar:yay'),
|
||||||
|
['12foo-bar', 'yay']
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
re.findall(base.patterns['safe_char'], 'a;b"*,cd'),
|
||||||
|
['a', 'b', '*', 'c', 'd']
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
re.findall(base.patterns['qsafe_char'], 'a;b"*,cd'),
|
||||||
|
['a', ';', 'b', '*', ',', 'c', 'd']
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
re.findall(base.patterns['param_value'],
|
||||||
|
'"quoted";not-quoted;start"after-illegal-quote',
|
||||||
|
re.VERBOSE),
|
||||||
|
['"quoted"', '', 'not-quoted', '', 'start', '',
|
||||||
|
'after-illegal-quote', '']
|
||||||
|
)
|
||||||
|
match = base.line_re.match('TEST;ALTREP="http://www.wiz.org":value:;"')
|
||||||
|
self.assertEqual(
|
||||||
|
match.group('value'),
|
||||||
|
'value:;"'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
match.group('name'),
|
||||||
|
'TEST'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
match.group('params'),
|
||||||
|
';ALTREP="http://www.wiz.org"'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_stringToTextValues(self):
|
||||||
|
"""
|
||||||
|
Test string lists
|
||||||
|
"""
|
||||||
|
self.assertEqual(
|
||||||
|
stringToTextValues(''),
|
||||||
|
['']
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
stringToTextValues('abcd,efgh'),
|
||||||
|
['abcd', 'efgh']
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_stringToPeriod(self):
|
||||||
|
"""
|
||||||
|
Test datetime strings
|
||||||
|
"""
|
||||||
|
self.assertEqual(
|
||||||
|
stringToPeriod("19970101T180000Z/19970102T070000Z"),
|
||||||
|
(datetime.datetime(1997, 1, 1, 18, 0, tzinfo=tzutc()),
|
||||||
|
datetime.datetime(1997, 1, 2, 7, 0, tzinfo=tzutc()))
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
stringToPeriod("19970101T180000Z/PT1H"),
|
||||||
|
(datetime.datetime(1997, 1, 1, 18, 0, tzinfo=tzutc()),
|
||||||
|
datetime.timedelta(0, 3600))
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_timedeltaToString(self):
|
||||||
|
"""
|
||||||
|
Test timedelta strings
|
||||||
|
"""
|
||||||
|
self.assertEqual(
|
||||||
|
timedeltaToString(two_hours),
|
||||||
|
'PT2H'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
timedeltaToString(datetime.timedelta(minutes=20)),
|
||||||
|
'PT20M'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_vtimezone_creation(self):
|
||||||
|
"""
|
||||||
|
Test timezones
|
||||||
|
"""
|
||||||
|
tzs = dateutil.tz.tzical(get_test_filepath("timezones.ics"))
|
||||||
|
pacific = icalendar.TimezoneComponent(tzs.get('US/Pacific'))
|
||||||
|
self.assertEqual(
|
||||||
|
str(pacific),
|
||||||
|
"<VTIMEZONE | <TZID{}US/Pacific>>"
|
||||||
|
)
|
||||||
|
santiago = icalendar.TimezoneComponent(tzs.get('Santiago'))
|
||||||
|
self.assertEqual(
|
||||||
|
str(santiago),
|
||||||
|
"<VTIMEZONE | <TZID{}Santiago>>"
|
||||||
|
)
|
||||||
|
for year in range(2001, 2010):
|
||||||
|
for month in (2, 9):
|
||||||
|
dt = datetime.datetime(year, month, 15,
|
||||||
|
tzinfo=tzs.get('Santiago'))
|
||||||
|
self.assertTrue(dt.replace(tzinfo=tzs.get('Santiago')), dt)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def test_timezone_serializing():
|
||||||
|
"""
|
||||||
|
Serializing with timezones test
|
||||||
|
"""
|
||||||
|
tzs = dateutil.tz.tzical(get_test_filepath("timezones.ics"))
|
||||||
|
pacific = tzs.get('US/Pacific')
|
||||||
|
cal = base.Component('VCALENDAR')
|
||||||
|
cal.setBehavior(icalendar.VCalendar2_0)
|
||||||
|
ev = cal.add('vevent')
|
||||||
|
ev.add('dtstart').value = datetime.datetime(2005, 10, 12, 9,
|
||||||
|
tzinfo=pacific)
|
||||||
|
evruleset = rruleset()
|
||||||
|
evruleset.rrule(rrule(WEEKLY, interval=2, byweekday=[2,4],
|
||||||
|
until=datetime.datetime(2005, 12, 15, 9)))
|
||||||
|
evruleset.rrule(rrule(MONTHLY, bymonthday=[-1,-5]))
|
||||||
|
evruleset.exdate(datetime.datetime(2005, 10, 14, 9, tzinfo=pacific))
|
||||||
|
ev.rruleset = evruleset
|
||||||
|
ev.add('duration').value = datetime.timedelta(hours=1)
|
||||||
|
|
||||||
|
apple = tzs.get('America/Montreal')
|
||||||
|
ev.dtstart.value = datetime.datetime(2005, 10, 12, 9, tzinfo=apple)
|
||||||
|
|
||||||
|
def test_pytz_timezone_serializing(self):
|
||||||
|
"""
|
||||||
|
Serializing with timezones from pytz test
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import pytz
|
||||||
|
except ImportError:
|
||||||
|
return self.skipTest("pytz not installed") # NOQA
|
||||||
|
|
||||||
|
# Avoid conflicting cached tzinfo from other tests
|
||||||
|
def unregister_tzid(tzid):
|
||||||
|
"""Clear tzid from icalendar TZID registry"""
|
||||||
|
if icalendar.getTzid(tzid, False):
|
||||||
|
icalendar.registerTzid(tzid, None)
|
||||||
|
|
||||||
|
unregister_tzid('US/Eastern')
|
||||||
|
eastern = pytz.timezone('US/Eastern')
|
||||||
|
cal = base.Component('VCALENDAR')
|
||||||
|
cal.setBehavior(icalendar.VCalendar2_0)
|
||||||
|
ev = cal.add('vevent')
|
||||||
|
ev.add('dtstart').value = eastern.localize(
|
||||||
|
datetime.datetime(2008, 10, 12, 9))
|
||||||
|
serialized = cal.serialize()
|
||||||
|
|
||||||
|
expected_vtimezone = get_test_file("tz_us_eastern.ics")
|
||||||
|
self.assertIn(
|
||||||
|
expected_vtimezone.replace('\r\n', '\n'),
|
||||||
|
serialized.replace('\r\n', '\n')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Exhaustively test all zones (just looking for no errors)
|
||||||
|
for tzname in pytz.all_timezones:
|
||||||
|
unregister_tzid(tzname)
|
||||||
|
tz = icalendar.TimezoneComponent(tzinfo=pytz.timezone(tzname))
|
||||||
|
tz.serialize()
|
||||||
|
|
||||||
|
def test_freeBusy(self):
|
||||||
|
"""
|
||||||
|
Test freebusy components
|
||||||
|
"""
|
||||||
|
test_cal = get_test_file("freebusy.ics")
|
||||||
|
|
||||||
|
vfb = base.newFromBehavior('VFREEBUSY')
|
||||||
|
vfb.add('uid').value = 'test'
|
||||||
|
vfb.add('dtstamp').value = datetime.datetime(2006, 2, 15, 0, tzinfo=utc)
|
||||||
|
vfb.add('dtstart').value = datetime.datetime(2006, 2, 16, 1, tzinfo=utc)
|
||||||
|
vfb.add('dtend').value = vfb.dtstart.value + two_hours
|
||||||
|
vfb.add('freebusy').value = [(vfb.dtstart.value, two_hours / 2)]
|
||||||
|
vfb.add('freebusy').value = [(vfb.dtstart.value, vfb.dtend.value)]
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
vfb.serialize().replace('\r\n', '\n'),
|
||||||
|
test_cal.replace('\r\n', '\n')
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_availablity(self):
|
||||||
|
"""
|
||||||
|
Test availability components
|
||||||
|
"""
|
||||||
|
test_cal = get_test_file("availablity.ics")
|
||||||
|
|
||||||
|
vcal = base.newFromBehavior('VAVAILABILITY')
|
||||||
|
vcal.add('uid').value = 'test'
|
||||||
|
vcal.add('dtstamp').value = datetime.datetime(2006, 2, 15, 0, tzinfo=utc)
|
||||||
|
vcal.add('dtstart').value = datetime.datetime(2006, 2, 16, 0, tzinfo=utc)
|
||||||
|
vcal.add('dtend').value = datetime.datetime(2006, 2, 17, 0, tzinfo=utc)
|
||||||
|
vcal.add('busytype').value = "BUSY"
|
||||||
|
|
||||||
|
av = base.newFromBehavior('AVAILABLE')
|
||||||
|
av.add('uid').value = 'test1'
|
||||||
|
av.add('dtstamp').value = datetime.datetime(2006, 2, 15, 0, tzinfo=utc)
|
||||||
|
av.add('dtstart').value = datetime.datetime(2006, 2, 16, 9, tzinfo=utc)
|
||||||
|
av.add('dtend').value = datetime.datetime(2006, 2, 16, 12, tzinfo=utc)
|
||||||
|
av.add('summary').value = "Available in the morning"
|
||||||
|
|
||||||
|
vcal.add(av)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
vcal.serialize().replace('\r\n', '\n'),
|
||||||
|
test_cal.replace('\r\n', '\n')
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_recurrence(self):
|
||||||
|
"""
|
||||||
|
Ensure date valued UNTILs in rrules are in a reasonable timezone,
|
||||||
|
and include that day (12/28 in this test)
|
||||||
|
"""
|
||||||
|
test_file = get_test_file("recurrence.ics")
|
||||||
|
cal = base.readOne(test_file)
|
||||||
|
dates = list(cal.vevent.getrruleset())
|
||||||
|
self.assertEqual(
|
||||||
|
dates[0],
|
||||||
|
datetime.datetime(2006, 1, 26, 23, 0, tzinfo=tzutc())
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
dates[1],
|
||||||
|
datetime.datetime(2006, 2, 23, 23, 0, tzinfo=tzutc())
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
dates[-1],
|
||||||
|
datetime.datetime(2006, 12, 28, 23, 0, tzinfo=tzutc())
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_recurring_component(self):
|
||||||
|
"""
|
||||||
|
Test recurring events
|
||||||
|
"""
|
||||||
|
vevent = RecurringComponent(name='VEVENT')
|
||||||
|
|
||||||
|
# init
|
||||||
|
self.assertTrue(vevent.isNative)
|
||||||
|
|
||||||
|
# rruleset should be None at this point.
|
||||||
|
# No rules have been passed or created.
|
||||||
|
self.assertEqual(vevent.rruleset, None)
|
||||||
|
|
||||||
|
# Now add start and rule for recurring event
|
||||||
|
vevent.add('dtstart').value = datetime.datetime(2005, 1, 19, 9)
|
||||||
|
vevent.add('rrule').value =u"FREQ=WEEKLY;COUNT=2;INTERVAL=2;BYDAY=TU,TH"
|
||||||
|
self.assertEqual(
|
||||||
|
list(vevent.rruleset),
|
||||||
|
[datetime.datetime(2005, 1, 20, 9, 0), datetime.datetime(2005, 2, 1, 9, 0)]
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
list(vevent.getrruleset(addRDate=True)),
|
||||||
|
[datetime.datetime(2005, 1, 19, 9, 0), datetime.datetime(2005, 1, 20, 9, 0)]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Also note that dateutil will expand all-day events (datetime.date values)
|
||||||
|
# to datetime.datetime value with time 0 and no timezone.
|
||||||
|
vevent.dtstart.value = datetime.date(2005,3,18)
|
||||||
|
self.assertEqual(
|
||||||
|
list(vevent.rruleset),
|
||||||
|
[datetime.datetime(2005, 3, 29, 0, 0), datetime.datetime(2005, 3, 31, 0, 0)]
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
list(vevent.getrruleset(True)),
|
||||||
|
[datetime.datetime(2005, 3, 18, 0, 0), datetime.datetime(2005, 3, 29, 0, 0)]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_recurrence_without_tz(self):
|
||||||
|
"""
|
||||||
|
Test recurring vevent missing any time zone definitions.
|
||||||
|
"""
|
||||||
|
test_file = get_test_file("recurrence-without-tz.ics")
|
||||||
|
cal = base.readOne(test_file)
|
||||||
|
dates = list(cal.vevent.getrruleset())
|
||||||
|
self.assertEqual(dates[0], datetime.datetime(2013, 1, 17, 0, 0))
|
||||||
|
self.assertEqual(dates[1], datetime.datetime(2013, 1, 24, 0, 0))
|
||||||
|
self.assertEqual(dates[-1], datetime.datetime(2013, 3, 28, 0, 0))
|
||||||
|
|
||||||
|
def test_recurrence_offset_naive(self):
|
||||||
|
"""
|
||||||
|
Ensure recurring vevent missing some time zone definitions is
|
||||||
|
parsing. See isseu #75.
|
||||||
|
"""
|
||||||
|
test_file = get_test_file("recurrence-offset-naive.ics")
|
||||||
|
cal = base.readOne(test_file)
|
||||||
|
dates = list(cal.vevent.getrruleset())
|
||||||
|
self.assertEqual(dates[0], datetime.datetime(2013, 1, 17, 0, 0))
|
||||||
|
self.assertEqual(dates[1], datetime.datetime(2013, 1, 24, 0, 0))
|
||||||
|
self.assertEqual(dates[-1], datetime.datetime(2013, 3, 28, 0, 0))
|
||||||
|
|
||||||
|
|
||||||
|
class TestChangeTZ(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Tests for change_tz.change_tz
|
||||||
|
"""
|
||||||
|
class StubCal(object):
|
||||||
|
class StubEvent(object):
|
||||||
|
class Node(object):
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __init__(self, dtstart, dtend):
|
||||||
|
self.dtstart = self.Node(dtstart)
|
||||||
|
self.dtend = self.Node(dtend)
|
||||||
|
|
||||||
|
def __init__(self, dates):
|
||||||
|
"""
|
||||||
|
dates is a list of tuples (dtstart, dtend)
|
||||||
|
"""
|
||||||
|
self.vevent_list = [self.StubEvent(*d) for d in dates]
|
||||||
|
|
||||||
|
def test_change_tz(self):
|
||||||
|
"""
|
||||||
|
Change the timezones of events in a component to a different
|
||||||
|
timezone
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Setup - create a stub vevent list
|
||||||
|
old_tz = dateutil.tz.gettz('UTC') # 0:00
|
||||||
|
new_tz = dateutil.tz.gettz('America/Chicago') # -5:00
|
||||||
|
|
||||||
|
dates = [
|
||||||
|
(datetime.datetime(1999, 12, 31, 23, 59, 59, 0, tzinfo=old_tz),
|
||||||
|
datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=old_tz)),
|
||||||
|
(datetime.datetime(2010, 12, 31, 23, 59, 59, 0, tzinfo=old_tz),
|
||||||
|
datetime.datetime(2011, 1, 2, 3, 0, 0, 0, tzinfo=old_tz))]
|
||||||
|
|
||||||
|
cal = self.StubCal(dates)
|
||||||
|
|
||||||
|
# Exercise - change the timezone
|
||||||
|
change_tz(cal, new_tz, dateutil.tz.gettz('UTC'))
|
||||||
|
|
||||||
|
# Test - that the tzs were converted correctly
|
||||||
|
expected_new_dates = [
|
||||||
|
(datetime.datetime(1999, 12, 31, 17, 59, 59, 0, tzinfo=new_tz),
|
||||||
|
datetime.datetime(1999, 12, 31, 18, 0, 0, 0, tzinfo=new_tz)),
|
||||||
|
(datetime.datetime(2010, 12, 31, 17, 59, 59, 0, tzinfo=new_tz),
|
||||||
|
datetime.datetime(2011, 1, 1, 21, 0, 0, 0, tzinfo=new_tz))]
|
||||||
|
|
||||||
|
for vevent, expected_datepair in zip(cal.vevent_list,
|
||||||
|
expected_new_dates):
|
||||||
|
self.assertEqual(vevent.dtstart.value, expected_datepair[0])
|
||||||
|
self.assertEqual(vevent.dtend.value, expected_datepair[1])
|
||||||
|
|
||||||
|
def test_change_tz_utc_only(self):
|
||||||
|
"""
|
||||||
|
Change any UTC timezones of events in a component to a different
|
||||||
|
timezone
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Setup - create a stub vevent list
|
||||||
|
utc_tz = dateutil.tz.gettz('UTC') # 0:00
|
||||||
|
non_utc_tz = dateutil.tz.gettz('America/Santiago') # -4:00
|
||||||
|
new_tz = dateutil.tz.gettz('America/Chicago') # -5:00
|
||||||
|
|
||||||
|
dates = [
|
||||||
|
(datetime.datetime(1999, 12, 31, 23, 59, 59, 0, tzinfo=utc_tz),
|
||||||
|
datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=non_utc_tz))]
|
||||||
|
|
||||||
|
cal = self.StubCal(dates)
|
||||||
|
|
||||||
|
# Exercise - change the timezone passing utc_only=True
|
||||||
|
change_tz(cal, new_tz, dateutil.tz.gettz('UTC'), utc_only=True)
|
||||||
|
|
||||||
|
# Test - that only the utc item has changed
|
||||||
|
expected_new_dates = [
|
||||||
|
(datetime.datetime(1999, 12, 31, 17, 59, 59, 0, tzinfo=new_tz),
|
||||||
|
dates[0][1])]
|
||||||
|
|
||||||
|
for vevent, expected_datepair in zip(cal.vevent_list,
|
||||||
|
expected_new_dates):
|
||||||
|
self.assertEqual(vevent.dtstart.value, expected_datepair[0])
|
||||||
|
self.assertEqual(vevent.dtend.value, expected_datepair[1])
|
||||||
|
|
||||||
|
def test_change_tz_default(self):
|
||||||
|
"""
|
||||||
|
Change the timezones of events in a component to a different
|
||||||
|
timezone, passing a default timezone that is assumed when the events
|
||||||
|
don't have one
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Setup - create a stub vevent list
|
||||||
|
new_tz = dateutil.tz.gettz('America/Chicago') # -5:00
|
||||||
|
|
||||||
|
dates = [
|
||||||
|
(datetime.datetime(1999, 12, 31, 23, 59, 59, 0, tzinfo=None),
|
||||||
|
datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=None))]
|
||||||
|
|
||||||
|
cal = self.StubCal(dates)
|
||||||
|
|
||||||
|
# Exercise - change the timezone
|
||||||
|
change_tz(cal, new_tz, dateutil.tz.gettz('UTC'))
|
||||||
|
|
||||||
|
# Test - that the tzs were converted correctly
|
||||||
|
expected_new_dates = [
|
||||||
|
(datetime.datetime(1999, 12, 31, 17, 59, 59, 0, tzinfo=new_tz),
|
||||||
|
datetime.datetime(1999, 12, 31, 18, 0, 0, 0, tzinfo=new_tz))]
|
||||||
|
|
||||||
|
for vevent, expected_datepair in zip(cal.vevent_list,
|
||||||
|
expected_new_dates):
|
||||||
|
self.assertEqual(vevent.dtstart.value, expected_datepair[0])
|
||||||
|
self.assertEqual(vevent.dtend.value, expected_datepair[1])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
14
radicale_vobject/tests/test_files/availablity.ics
Normal file
14
radicale_vobject/tests/test_files/availablity.ics
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
BEGIN:VAVAILABILITY
|
||||||
|
UID:test
|
||||||
|
DTSTART:20060216T000000Z
|
||||||
|
DTEND:20060217T000000Z
|
||||||
|
BEGIN:AVAILABLE
|
||||||
|
UID:test1
|
||||||
|
DTSTART:20060216T090000Z
|
||||||
|
DTEND:20060216T120000Z
|
||||||
|
DTSTAMP:20060215T000000Z
|
||||||
|
SUMMARY:Available in the morning
|
||||||
|
END:AVAILABLE
|
||||||
|
BUSYTYPE:BUSY
|
||||||
|
DTSTAMP:20060215T000000Z
|
||||||
|
END:VAVAILABILITY
|
10
radicale_vobject/tests/test_files/badline.ics
Normal file
10
radicale_vobject/tests/test_files/badline.ics
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
METHOD:PUBLISH
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART:19870405T020000
|
||||||
|
X-BAD/SLASH:TRUE
|
||||||
|
X-BAD_UNDERSCORE:TRUE
|
||||||
|
UID:EC9439B1-FF65-11D6-9973-003065F99D04
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
16
radicale_vobject/tests/test_files/badstream.ics
Normal file
16
radicale_vobject/tests/test_files/badstream.ics
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
X-WR-TIMEZONE;VALUE=TEXT:US/Pacific
|
||||||
|
METHOD:PUBLISH
|
||||||
|
PRODID:-//Apple Computer\, Inc//iCal 1.0//EN
|
||||||
|
X-WR-CALNAME;VALUE=TEXT:Example
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART:20021028T140000Z
|
||||||
|
BEGIN:VALARM
|
||||||
|
TRIGGER:a20021028120000
|
||||||
|
ACTION:DISPLAY
|
||||||
|
DESCRIPTION:This trigger has a nonsensical value
|
||||||
|
END:VALARM
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
8
radicale_vobject/tests/test_files/freebusy.ics
Normal file
8
radicale_vobject/tests/test_files/freebusy.ics
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
BEGIN:VFREEBUSY
|
||||||
|
UID:test
|
||||||
|
DTSTART:20060216T010000Z
|
||||||
|
DTEND:20060216T030000Z
|
||||||
|
DTSTAMP:20060215T000000Z
|
||||||
|
FREEBUSY:20060216T010000Z/PT1H
|
||||||
|
FREEBUSY:20060216T010000Z/20060216T030000Z
|
||||||
|
END:VFREEBUSY
|
15
radicale_vobject/tests/test_files/journal.ics
Normal file
15
radicale_vobject/tests/test_files/journal.ics
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
BEGIN:VJOURNAL
|
||||||
|
UID:19970901T130000Z-123405@example.com
|
||||||
|
DTSTAMP:19970901T130000Z
|
||||||
|
DTSTART;VALUE=DATE:19970317
|
||||||
|
SUMMARY:Staff meeting minutes
|
||||||
|
DESCRIPTION:1. Staff meeting: Participants include Joe\,
|
||||||
|
Lisa\, and Bob. Aurora project plans were reviewed.
|
||||||
|
There is currently no budget reserves for this project.
|
||||||
|
Lisa will escalate to management. Next meeting on Tuesday.\n
|
||||||
|
2. Telephone Conference: ABC Corp. sales representative
|
||||||
|
called to discuss new printer. Promised to get us a demo by
|
||||||
|
Friday.\n3. Henry Miller (Handsoff Insurance): Car was
|
||||||
|
totaled by tree. Is looking into a loaner car. 555-2323
|
||||||
|
(tel).
|
||||||
|
END:VJOURNAL
|
85
radicale_vobject/tests/test_files/more_tests.txt
Normal file
85
radicale_vobject/tests/test_files/more_tests.txt
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
|
||||||
|
Unicode in vCards
|
||||||
|
.................
|
||||||
|
|
||||||
|
>>> import vobject
|
||||||
|
>>> card = vobject.vCard()
|
||||||
|
>>> card.add('fn').value = u'Hello\u1234 World!'
|
||||||
|
>>> card.add('n').value = vobject.vcard.Name('World', u'Hello\u1234')
|
||||||
|
>>> card.add('adr').value = vobject.vcard.Address(u'5\u1234 Nowhere, Apt 1', 'Berkeley', 'CA', '94704', 'USA')
|
||||||
|
>>> card
|
||||||
|
<VCARD| [<ADR{}5? Nowhere, Apt 1\nBerkeley, CA 94704\nUSA>, <FN{}Hello? World!>, <N{} Hello? World >]>
|
||||||
|
>>> card.serialize()
|
||||||
|
u'BEGIN:VCARD\r\nVERSION:3.0\r\nADR:;;5\u1234 Nowhere\\, Apt 1;Berkeley;CA;94704;USA\r\nFN:Hello\u1234 World!\r\nN:World;Hello\u1234;;;\r\nEND:VCARD\r\n'
|
||||||
|
>>> print(card.serialize())
|
||||||
|
BEGIN:VCARD
|
||||||
|
VERSION:3.0
|
||||||
|
ADR:;;5ሴ Nowhere\, Apt 1;Berkeley;CA;94704;USA
|
||||||
|
FN:Helloሴ World!
|
||||||
|
N:World;Helloሴ;;;
|
||||||
|
END:VCARD
|
||||||
|
|
||||||
|
Helper function
|
||||||
|
...............
|
||||||
|
>>> from pkg_resources import resource_stream
|
||||||
|
>>> def get_stream(path):
|
||||||
|
... try:
|
||||||
|
... return resource_stream(__name__, 'test_files/' + path)
|
||||||
|
... except: # different paths, depending on whether doctest is run directly
|
||||||
|
... return resource_stream(__name__, path)
|
||||||
|
|
||||||
|
Unicode in TZID
|
||||||
|
...............
|
||||||
|
>>> f = get_stream("tzid_8bit.ics")
|
||||||
|
>>> cal = vobject.readOne(f)
|
||||||
|
>>> print(cal.vevent.dtstart.value)
|
||||||
|
2008-05-30 15:00:00+06:00
|
||||||
|
>>> print(cal.vevent.dtstart.serialize())
|
||||||
|
DTSTART;TZID=Екатеринбург:20080530T150000
|
||||||
|
|
||||||
|
Commas in TZID
|
||||||
|
..............
|
||||||
|
>>> f = get_stream("ms_tzid.ics")
|
||||||
|
>>> cal = vobject.readOne(f)
|
||||||
|
>>> print(cal.vevent.dtstart.value)
|
||||||
|
2008-05-30 15:00:00+10:00
|
||||||
|
|
||||||
|
Equality in vCards
|
||||||
|
..................
|
||||||
|
|
||||||
|
>>> card.adr.value == vobject.vcard.Address('Just a street')
|
||||||
|
False
|
||||||
|
>>> card.adr.value == vobject.vcard.Address(u'5\u1234 Nowhere, Apt 1', 'Berkeley', 'CA', '94704', 'USA')
|
||||||
|
True
|
||||||
|
|
||||||
|
Organization (org)
|
||||||
|
..................
|
||||||
|
|
||||||
|
>>> card.add('org').value = ["Company, Inc.", "main unit", "sub-unit"]
|
||||||
|
>>> print(card.org.serialize())
|
||||||
|
ORG:Company\, Inc.;main unit;sub-unit
|
||||||
|
|
||||||
|
Ruby escapes semi-colons in rrules
|
||||||
|
..................................
|
||||||
|
|
||||||
|
>>> f = get_stream("ruby_rrule.ics")
|
||||||
|
>>> cal = vobject.readOne(f)
|
||||||
|
>>> iter(cal.vevent.rruleset).next()
|
||||||
|
datetime.datetime(2003, 1, 1, 7, 0)
|
||||||
|
|
||||||
|
quoted-printable
|
||||||
|
................
|
||||||
|
|
||||||
|
>>> vcf = 'BEGIN:VCARD\nVERSION:2.1\nN;ENCODING=QUOTED-PRINTABLE:;=E9\nFN;ENCODING=QUOTED-PRINTABLE:=E9\nTEL;HOME:0111111111\nEND:VCARD\n\n'
|
||||||
|
>>> vcf = vobject.readOne(vcf)
|
||||||
|
>>> vcf.n.value
|
||||||
|
<Name: ? >
|
||||||
|
>>> vcf.n.value.given
|
||||||
|
u'\xe9'
|
||||||
|
>>> vcf.serialize()
|
||||||
|
'BEGIN:VCARD\r\nVERSION:2.1\r\nFN:\xc3\xa9\r\nN:;\xc3\xa9;;;\r\nTEL:0111111111\r\nEND:VCARD\r\n'
|
||||||
|
|
||||||
|
>>> vcs = 'BEGIN:VCALENDAR\r\nPRODID:-//OpenSync//NONSGML OpenSync vformat 0.3//EN\r\nVERSION:1.0\r\nBEGIN:VEVENT\r\nDESCRIPTION;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:foo =C3=A5=0Abar =C3=A4=\r\n=0Abaz =C3=B6\r\nUID:20080406T152030Z-7822\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n'
|
||||||
|
>>> vcs = vobject.readOne(vcs, allowQP = True)
|
||||||
|
>>> vcs.serialize()
|
||||||
|
'BEGIN:VCALENDAR\r\nVERSION:1.0\r\nPRODID:-//OpenSync//NONSGML OpenSync vformat 0.3//EN\r\nBEGIN:VEVENT\r\nUID:20080406T152030Z-7822\r\nDESCRIPTION:foo \xc3\xa5\\nbar \xc3\xa4\\nbaz \xc3\xb6\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n'
|
39
radicale_vobject/tests/test_files/ms_tzid.ics
Normal file
39
radicale_vobject/tests/test_files/ms_tzid.ics
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Canberra, Melbourne, Sydney
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:20010325T020000
|
||||||
|
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3;UNTIL=20050327T070000Z
|
||||||
|
TZOFFSETFROM:+1100
|
||||||
|
TZOFFSETTO:+1000
|
||||||
|
TZNAME:Standard Time
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:20060402T020000
|
||||||
|
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=4;UNTIL=20060402T070000Z
|
||||||
|
TZOFFSETFROM:+1100
|
||||||
|
TZOFFSETTO:+1000
|
||||||
|
TZNAME:Standard Time
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:20070325T020000
|
||||||
|
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
|
||||||
|
TZOFFSETFROM:+1100
|
||||||
|
TZOFFSETTO:+1000
|
||||||
|
TZNAME:Standard Time
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:20001029T020000
|
||||||
|
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
|
||||||
|
TZOFFSETFROM:+1000
|
||||||
|
TZOFFSETTO:+1100
|
||||||
|
TZNAME:Daylight Savings Time
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:CommaTest
|
||||||
|
DTSTART;TZID="Canberra, Melbourne, Sydney":20080530T150000
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
@ -0,0 +1,9 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART;VALUE=DATE:20130117
|
||||||
|
DTEND;VALUE=DATE:20130118
|
||||||
|
RRULE:FREQ=WEEKLY;UNTIL=20130330T230000Z;BYDAY=TH
|
||||||
|
SUMMARY:Meeting
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
@ -0,0 +1,9 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART;VALUE=DATE:20130117
|
||||||
|
DTEND;VALUE=DATE:20130118
|
||||||
|
RRULE:FREQ=WEEKLY;UNTIL=20130330;BYDAY=TH
|
||||||
|
SUMMARY:Meeting
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
30
radicale_vobject/tests/test_files/recurrence.ics
Normal file
30
radicale_vobject/tests/test_files/recurrence.ics
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION
|
||||||
|
:2.0
|
||||||
|
PRODID
|
||||||
|
:-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED
|
||||||
|
:20060327T214227Z
|
||||||
|
LAST-MODIFIED
|
||||||
|
:20060313T080829Z
|
||||||
|
DTSTAMP
|
||||||
|
:20060116T231602Z
|
||||||
|
UID
|
||||||
|
:70922B3051D34A9E852570EC00022388
|
||||||
|
SUMMARY
|
||||||
|
:Monthly - All Hands Meeting with Joe Smith
|
||||||
|
STATUS
|
||||||
|
:CONFIRMED
|
||||||
|
CLASS
|
||||||
|
:PUBLIC
|
||||||
|
RRULE
|
||||||
|
:FREQ=MONTHLY;UNTIL=20061228;INTERVAL=1;BYDAY=4TH
|
||||||
|
DTSTART
|
||||||
|
:20060126T230000Z
|
||||||
|
DTEND
|
||||||
|
:20060127T000000Z
|
||||||
|
DESCRIPTION
|
||||||
|
:Repeat Meeting: - Occurs every 4th Thursday of each month
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
16
radicale_vobject/tests/test_files/ruby_rrule.ics
Normal file
16
radicale_vobject/tests/test_files/ruby_rrule.ics
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
METHOD:PUBLISH
|
||||||
|
PRODID:-//LinkeSOFT GmbH//NONSGML DIMEX//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
SEQUENCE:0
|
||||||
|
RRULE:FREQ=DAILY\;COUNT=10
|
||||||
|
DTEND:20030101T080000
|
||||||
|
UID:2008-05-29T17:31:42+02:00_865561242
|
||||||
|
CATEGORIES:Unfiled
|
||||||
|
SUMMARY:Something
|
||||||
|
DTSTART:20030101T070000
|
||||||
|
DTSTAMP:20080529T152100
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
5
radicale_vobject/tests/test_files/silly_test.ics
Normal file
5
radicale_vobject/tests/test_files/silly_test.ics
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
sillyname:name
|
||||||
|
profile:sillyprofile
|
||||||
|
stuff:folded
|
||||||
|
line
|
||||||
|
morestuff;asinine:this line is not folded, but in practice probably ought to be, as it is exceptionally long, and moreover demonstratively stupid
|
11
radicale_vobject/tests/test_files/simple_2_0_test.ics
Normal file
11
radicale_vobject/tests/test_files/simple_2_0_test.ics
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//PYVOBJECT//NONSGML Version 1//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:Not very random UID
|
||||||
|
DTSTART:20060509T000000
|
||||||
|
CREATED:20060101T180000Z
|
||||||
|
DESCRIPTION:Test event
|
||||||
|
DTSTAMP:20170626T000000Z
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
13
radicale_vobject/tests/test_files/simple_3_0_test.ics
Normal file
13
radicale_vobject/tests/test_files/simple_3_0_test.ics
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
BEGIN:VCARD
|
||||||
|
VERSION:3.0
|
||||||
|
FN:Daffy Duck Knudson (with Bugs Bunny and Mr. Pluto)
|
||||||
|
N:Knudson;Daffy Duck (with Bugs Bunny and Mr. Pluto)
|
||||||
|
NICKNAME:gnat and gnu and pluto
|
||||||
|
BDAY;value=date:02-10
|
||||||
|
TEL;type=HOME:+01-(0)2-765.43.21
|
||||||
|
TEL;type=CELL:+01-(0)5-555.55.55
|
||||||
|
ACCOUNT;type=HOME:010-1234567-05
|
||||||
|
ADR;type=HOME:;;Haight Street 512\;\nEscape\, Test;Novosibirsk;;80214;Gnuland
|
||||||
|
TEL;type=HOME:+01-(0)2-876.54.32
|
||||||
|
ORG:University of Novosibirsk;Department of Octopus Parthenogenesis
|
||||||
|
END:VCARD
|
5
radicale_vobject/tests/test_files/simple_test.ics
Normal file
5
radicale_vobject/tests/test_files/simple_test.ics
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
BEGIN:VEVENT
|
||||||
|
SUMMARY;blah=hi!:Bastille Day Party
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
41
radicale_vobject/tests/test_files/standard_test.ics
Normal file
41
radicale_vobject/tests/test_files/standard_test.ics
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
X-WR-TIMEZONE;VALUE=TEXT:US/Pacific
|
||||||
|
METHOD:PUBLISH
|
||||||
|
PRODID:-//Apple Computer\, Inc//iCal 1.0//EN
|
||||||
|
X-WR-CALNAME;VALUE=TEXT:Example
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
SEQUENCE:5
|
||||||
|
DTSTART;TZID=US/Pacific:20021028T140000
|
||||||
|
RRULE:FREQ=Weekly;COUNT=10
|
||||||
|
DTSTAMP:20021028T011706Z
|
||||||
|
SUMMARY:Coffee with Jason
|
||||||
|
UID:EC9439B1-FF65-11D6-9973-003065F99D04
|
||||||
|
DTEND;TZID=US/Pacific:20021028T150000
|
||||||
|
BEGIN:VALARM
|
||||||
|
TRIGGER;VALUE=DURATION:-P1D
|
||||||
|
ACTION:DISPLAY
|
||||||
|
DESCRIPTION:Event reminder\, with comma\nand line feed
|
||||||
|
END:VALARM
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
X-LIC-LOCATION:Random location
|
||||||
|
TZID:US/Pacific
|
||||||
|
LAST-MODIFIED:19870101T000000Z
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:19671029T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
|
||||||
|
TZOFFSETFROM:-0700
|
||||||
|
TZOFFSETTO:-0800
|
||||||
|
TZNAME:PST
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:19870405T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
|
||||||
|
TZOFFSETFROM:-0800
|
||||||
|
TZOFFSETTO:-0700
|
||||||
|
TZNAME:PDT
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
||||||
|
END:VCALENDAR
|
107
radicale_vobject/tests/test_files/timezones.ics
Normal file
107
radicale_vobject/tests/test_files/timezones.ics
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:US/Pacific
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:19671029T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
|
||||||
|
TZOFFSETFROM:-0700
|
||||||
|
TZOFFSETTO:-0800
|
||||||
|
TZNAME:PST
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:19870405T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
|
||||||
|
TZOFFSETFROM:-0800
|
||||||
|
TZOFFSETTO:-0700
|
||||||
|
TZNAME:PDT
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
||||||
|
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:US/Eastern
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:19671029T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
|
||||||
|
TZOFFSETFROM:-0400
|
||||||
|
TZOFFSETTO:-0500
|
||||||
|
TZNAME:EST
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:19870405T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
|
||||||
|
TZOFFSETFROM:-0500
|
||||||
|
TZOFFSETTO:-0400
|
||||||
|
TZNAME:EDT
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
||||||
|
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Santiago
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:19700314T000000
|
||||||
|
TZOFFSETFROM:-0300
|
||||||
|
TZOFFSETTO:-0400
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SA
|
||||||
|
TZNAME:Pacific SA Standard Time
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:19701010T000000
|
||||||
|
TZOFFSETFROM:-0400
|
||||||
|
TZOFFSETTO:-0300
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=2SA
|
||||||
|
TZNAME:Pacific SA Daylight Time
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
||||||
|
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:W. Europe
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:19701025T030000
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
TZNAME:W. Europe Standard Time
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:19700329T020000
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
TZNAME:W. Europe Daylight Time
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
||||||
|
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:US/Fictitious-Eastern
|
||||||
|
LAST-MODIFIED:19870101T000000Z
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:19671029T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
|
||||||
|
TZOFFSETFROM:-0400
|
||||||
|
TZOFFSETTO:-0500
|
||||||
|
TZNAME:EST
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:19870405T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4;UNTIL=20050403T070000Z
|
||||||
|
TZOFFSETFROM:-0500
|
||||||
|
TZOFFSETTO:-0400
|
||||||
|
TZNAME:EDT
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
||||||
|
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:America/Montreal
|
||||||
|
LAST-MODIFIED:20051013T233643Z
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:20050403T070000
|
||||||
|
TZOFFSETTO:-0400
|
||||||
|
TZOFFSETFROM:+0000
|
||||||
|
TZNAME:EDT
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:20051030T020000
|
||||||
|
TZOFFSETTO:-0500
|
||||||
|
TZOFFSETFROM:-0400
|
||||||
|
TZNAME:EST
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
31
radicale_vobject/tests/test_files/tz_us_eastern.ics
Normal file
31
radicale_vobject/tests/test_files/tz_us_eastern.ics
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:US/Eastern
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:20001029T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10;UNTIL=20061029T060000Z
|
||||||
|
TZNAME:EST
|
||||||
|
TZOFFSETFROM:-0400
|
||||||
|
TZOFFSETTO:-0500
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:20071104T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
|
||||||
|
TZNAME:EST
|
||||||
|
TZOFFSETFROM:-0400
|
||||||
|
TZOFFSETTO:-0500
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:20000402T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4;UNTIL=20060402T070000Z
|
||||||
|
TZNAME:EDT
|
||||||
|
TZOFFSETFROM:-0500
|
||||||
|
TZOFFSETTO:-0400
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:20070311T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
|
||||||
|
TZNAME:EDT
|
||||||
|
TZOFFSETFROM:-0500
|
||||||
|
TZOFFSETTO:-0400
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
23
radicale_vobject/tests/test_files/tzid_8bit.ics
Normal file
23
radicale_vobject/tests/test_files/tzid_8bit.ics
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Екатеринбург
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:16011028T030000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
|
||||||
|
TZOFFSETFROM:+0600
|
||||||
|
TZOFFSETTO:+0500
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:16010325T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
|
||||||
|
TZOFFSETFROM:+0500
|
||||||
|
TZOFFSETTO:+0600
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:CyrillicTest
|
||||||
|
DTSTART;TZID=Екатеринбург:20080530T150000
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
39
radicale_vobject/tests/test_files/utf8_test.ics
Normal file
39
radicale_vobject/tests/test_files/utf8_test.ics
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
METHOD:PUBLISH
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
PRODID:-//EVDB//www.evdb.com//EN
|
||||||
|
VERSION:2.0
|
||||||
|
X-WR-CALNAME:EVDB Event Feed
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART:20060922T000100Z
|
||||||
|
DTEND:20060922T050100Z
|
||||||
|
DTSTAMP:20050914T163414Z
|
||||||
|
SUMMARY:The title こんにちはキティ
|
||||||
|
DESCRIPTION:hello\nHere is a description\n\n\nこんにちはキティ
|
||||||
|
\n\n\n\nZwei Java-schwere Entwicklerpositionen und irgendeine Art sond
|
||||||
|
erbar-klingende Netzsichtbarmachungöffnung\, an einer interessanten F
|
||||||
|
irma im Gebäude\, in dem ich angerufenen Semantic Research bearbeite.
|
||||||
|
1. Zauberer Des Semantica Software Engineer 2. Älterer Semantica Sof
|
||||||
|
tware-Englisch-3. Graph/Semantica Netz-Visualization/Navigation Sie ei
|
||||||
|
ngestufte Software-Entwicklung für die Regierung. Die Firma ist stark
|
||||||
|
und die Projekte sind sehr kühl und schließen irgendeinen Spielraum
|
||||||
|
ein. Wenn ich Ihnen irgendwie mehr erkläre\, muß ich Sie töten. Ps
|
||||||
|
. Tat schnell -- jemand ist\, wenn es hier interviewt\, wie ich dieses
|
||||||
|
schreibe. Er schaut intelligent (er trägt Kleidhosen) Semantica Soft
|
||||||
|
ware Engineer FIRMA: Semantische Forschung\, Inc. REPORTS ZU: Vizeprä
|
||||||
|
sident\, Produkt-Entwicklung POSITION: San Diego (Pint Loma) WEB SITE:
|
||||||
|
www.semanticresearch.com email: dorie@semanticresearch.com FIRMA-HINT
|
||||||
|
ERGRUND Semantische Forschung ist der führende Versorger der semantis
|
||||||
|
cher Netzwerkanschluß gegründeten nicht linearen Wissen Darstellung
|
||||||
|
Werkzeuge. Die Firma stellt diese Werkzeuge zum Intel\, zur reg.\, zum
|
||||||
|
EDU und zu den kommerziellen Märkten zur Verfügung. BRINGEN SIE ZUS
|
||||||
|
AMMENFASSUNG IN POSITION Semantische Forschung\, Inc. basiert in San D
|
||||||
|
iego\, Ca im alten realen Weltsan Diego Haus...\, das wir den Weltbest
|
||||||
|
en Platz haben zum zu arbeiten. Wir suchen nach Superstarentwicklern\,
|
||||||
|
um uns in der fortfahrenden Entwicklung unserer Semantica Produktseri
|
||||||
|
e zu unterstützen.
|
||||||
|
LOCATION:こんにちはキティ
|
||||||
|
SEQUENCE:0
|
||||||
|
UID:E0-001-000276068-2
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
18
radicale_vobject/tests/test_files/vcard_with_groups.ics
Normal file
18
radicale_vobject/tests/test_files/vcard_with_groups.ics
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
home.begin:vcard
|
||||||
|
version:3.0
|
||||||
|
source:ldap://cn=Meister%20Berger,o=Universitaet%20Goerlitz,c=DE
|
||||||
|
name:Meister Berger
|
||||||
|
fn:Meister Berger
|
||||||
|
n:Berger;Meister
|
||||||
|
bday;value=date:1963-09-21
|
||||||
|
o:Universit=E6t G=F6rlitz
|
||||||
|
title:Mayor
|
||||||
|
title;language=de;value=text:Burgermeister
|
||||||
|
note:The Mayor of the great city of
|
||||||
|
Goerlitz in the great country of Germany.\nNext line.
|
||||||
|
email;internet:mb@goerlitz.de
|
||||||
|
home.tel;type=fax,voice;type=msg:+49 3581 123456
|
||||||
|
home.label:Hufenshlagel 1234\n
|
||||||
|
02828 Goerlitz\n
|
||||||
|
Deutschland
|
||||||
|
END:VCARD
|
13
radicale_vobject/tests/test_files/vtodo.ics
Normal file
13
radicale_vobject/tests/test_files/vtodo.ics
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Example Corp.//CalDAV Client//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:20070313T123432Z-456553@example.com
|
||||||
|
DTSTAMP:20070313T123432Z
|
||||||
|
DUE;VALUE=DATE:20070501
|
||||||
|
SUMMARY:Submit Quebec Income Tax Return for 2006
|
||||||
|
CLASS:CONFIDENTIAL
|
||||||
|
CATEGORIES:FAMILY,FINANCE
|
||||||
|
STATUS:NEEDS-ACTION
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR
|
10
setup.cfg
10
setup.cfg
@ -5,5 +5,11 @@ test = pytest
|
|||||||
python-tag = py3
|
python-tag = py3
|
||||||
|
|
||||||
[tool:pytest]
|
[tool:pytest]
|
||||||
addopts = --flake8 --isort --cov radicale -r s
|
addopts = --flake8 --isort --cov radicale --cov radicale_vobject -r s
|
||||||
norecursedirs = dist .cache .git build Radicale.egg-info .eggs venv radicale_vobject
|
norecursedirs = dist .cache .git build Radicale.egg-info .eggs venv
|
||||||
|
|
||||||
|
[isort]
|
||||||
|
skip = radicale_vobject
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
exclude = radicale_vobject/*
|
||||||
|
Loading…
Reference in New Issue
Block a user