kopia lustrzana https://github.com/collective/icalendar
Ruff format tests (#719)
* ruff format tests * log changes * create missing pytz.UnknownTimeZoneError --------- Co-authored-by: Steve Piercy <web@stevepiercy.com>pull/734/head
rodzic
48d471eb29
commit
6efa2d3bc9
|
|
@ -6,6 +6,7 @@ Changelog
|
|||
|
||||
Minor changes:
|
||||
|
||||
- Format test code with Ruff. See `Issue 672 <https://github.com/collective/icalendar/issues/672>`_.
|
||||
- Document the Debian package. See `Issue 701 <https://github.com/collective/icalendar/issues/701>`_.
|
||||
|
||||
Breaking changes:
|
||||
|
|
|
|||
|
|
@ -3,25 +3,28 @@ try:
|
|||
except ImportError:
|
||||
import zoneinfo
|
||||
import pytest
|
||||
|
||||
import icalendar
|
||||
|
||||
try:
|
||||
import pytz
|
||||
except ImportError:
|
||||
pytz = None
|
||||
from datetime import datetime
|
||||
from dateutil import tz
|
||||
from icalendar.cal import Component, Calendar
|
||||
from icalendar.timezone import tzp as _tzp
|
||||
from icalendar.timezone import TZP
|
||||
from pathlib import Path
|
||||
import itertools
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from dateutil import tz
|
||||
|
||||
from icalendar.cal import Calendar, Component
|
||||
from icalendar.timezone import TZP
|
||||
from icalendar.timezone import tzp as _tzp
|
||||
|
||||
HAS_PYTZ = pytz is not None
|
||||
if HAS_PYTZ:
|
||||
PYTZ_UTC = [
|
||||
pytz.utc,
|
||||
pytz.timezone('UTC'),
|
||||
pytz.timezone("UTC"),
|
||||
]
|
||||
PYTZ_IN_TIMEZONE = [
|
||||
lambda dt, tzname: pytz.timezone(tzname).localize(dt),
|
||||
|
|
@ -34,14 +37,19 @@ else:
|
|||
|
||||
|
||||
class DataSource:
|
||||
'''A collection of parsed ICS elements (e.g calendars, timezones, events)'''
|
||||
def __init__(self, data_source_folder:Path, parser):
|
||||
"""A collection of parsed ICS elements (e.g calendars, timezones, events)"""
|
||||
|
||||
def __init__(self, data_source_folder: Path, parser):
|
||||
self._parser = parser
|
||||
self._data_source_folder = data_source_folder
|
||||
|
||||
def keys(self):
|
||||
"""Return all the files that could be used."""
|
||||
return [p.stem for p in self._data_source_folder.iterdir() if p.suffix.lower() == ".ics"]
|
||||
return [
|
||||
p.stem
|
||||
for p in self._data_source_folder.iterdir()
|
||||
if p.suffix.lower() == ".ics"
|
||||
]
|
||||
|
||||
def __getitem__(self, attribute):
|
||||
"""Parse a file and return the result stored in the attribute."""
|
||||
|
|
@ -49,11 +57,11 @@ class DataSource:
|
|||
source_file = attribute
|
||||
attribute = attribute[:-4]
|
||||
else:
|
||||
source_file = attribute + '.ics'
|
||||
source_file = attribute + ".ics"
|
||||
source_path = self._data_source_folder / source_file
|
||||
if not source_path.is_file():
|
||||
raise AttributeError(f"{source_path} does not exist.")
|
||||
with source_path.open('rb') as f:
|
||||
with source_path.open("rb") as f:
|
||||
raw_ics = f.read()
|
||||
source = self._parser(raw_ics)
|
||||
if not isinstance(source, list):
|
||||
|
|
@ -76,51 +84,67 @@ class DataSource:
|
|||
@property
|
||||
def multiple(self):
|
||||
"""Return a list of all components parsed."""
|
||||
return self.__class__(self._data_source_folder, lambda data: self._parser(data, multiple=True))
|
||||
return self.__class__(
|
||||
self._data_source_folder, lambda data: self._parser(data, multiple=True)
|
||||
)
|
||||
|
||||
|
||||
HERE = Path(__file__).parent
|
||||
CALENDARS_FOLDER = HERE / 'calendars'
|
||||
TIMEZONES_FOLDER = HERE / 'timezones'
|
||||
EVENTS_FOLDER = HERE / 'events'
|
||||
CALENDARS_FOLDER = HERE / "calendars"
|
||||
TIMEZONES_FOLDER = HERE / "timezones"
|
||||
EVENTS_FOLDER = HERE / "events"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def calendars(tzp):
|
||||
return DataSource(CALENDARS_FOLDER, icalendar.Calendar.from_ical)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def timezones(tzp):
|
||||
return DataSource(TIMEZONES_FOLDER, icalendar.Timezone.from_ical)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def events(tzp):
|
||||
return DataSource(EVENTS_FOLDER, icalendar.Event.from_ical)
|
||||
|
||||
@pytest.fixture(params=PYTZ_UTC + [
|
||||
zoneinfo.ZoneInfo('UTC'),
|
||||
tz.UTC,
|
||||
tz.gettz('UTC')])
|
||||
|
||||
@pytest.fixture(params=PYTZ_UTC + [zoneinfo.ZoneInfo("UTC"), tz.UTC, tz.gettz("UTC")])
|
||||
def utc(request, tzp):
|
||||
return request.param
|
||||
|
||||
@pytest.fixture(params=PYTZ_IN_TIMEZONE + [
|
||||
lambda dt, tzname: dt.replace(tzinfo=tz.gettz(tzname)),
|
||||
lambda dt, tzname: dt.replace(tzinfo=zoneinfo.ZoneInfo(tzname))
|
||||
])
|
||||
|
||||
@pytest.fixture(
|
||||
params=PYTZ_IN_TIMEZONE
|
||||
+ [
|
||||
lambda dt, tzname: dt.replace(tzinfo=tz.gettz(tzname)),
|
||||
lambda dt, tzname: dt.replace(tzinfo=zoneinfo.ZoneInfo(tzname)),
|
||||
]
|
||||
)
|
||||
def in_timezone(request, tzp):
|
||||
return request.param
|
||||
|
||||
|
||||
# exclude broken calendars here
|
||||
ICS_FILES_EXCLUDE = (
|
||||
"big_bad_calendar.ics", "issue_104_broken_calendar.ics", "small_bad_calendar.ics",
|
||||
"multiple_calendar_components.ics", "pr_480_summary_with_colon.ics",
|
||||
"parsing_error_in_UTC_offset.ics", "parsing_error.ics",
|
||||
"big_bad_calendar.ics",
|
||||
"issue_104_broken_calendar.ics",
|
||||
"small_bad_calendar.ics",
|
||||
"multiple_calendar_components.ics",
|
||||
"pr_480_summary_with_colon.ics",
|
||||
"parsing_error_in_UTC_offset.ics",
|
||||
"parsing_error.ics",
|
||||
)
|
||||
ICS_FILES = [
|
||||
file.name for file in
|
||||
itertools.chain(CALENDARS_FOLDER.iterdir(), TIMEZONES_FOLDER.iterdir(), EVENTS_FOLDER.iterdir())
|
||||
file.name
|
||||
for file in itertools.chain(
|
||||
CALENDARS_FOLDER.iterdir(), TIMEZONES_FOLDER.iterdir(), EVENTS_FOLDER.iterdir()
|
||||
)
|
||||
if file.name not in ICS_FILES_EXCLUDE
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(params=ICS_FILES)
|
||||
def ics_file(tzp, calendars, timezones, events, request):
|
||||
"""An example ICS file."""
|
||||
|
|
@ -133,71 +157,76 @@ def ics_file(tzp, calendars, timezones, events, request):
|
|||
|
||||
|
||||
FUZZ_V1 = [key for key in CALENDARS_FOLDER.iterdir() if "fuzz-testcase" in str(key)]
|
||||
|
||||
|
||||
@pytest.fixture(params=FUZZ_V1)
|
||||
def fuzz_v1_calendar(request):
|
||||
"""Clusterfuzz calendars."""
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def x_sometime():
|
||||
"""Map x_sometime to time"""
|
||||
icalendar.cal.types_factory.types_map['X-SOMETIME'] = 'time'
|
||||
icalendar.cal.types_factory.types_map["X-SOMETIME"] = "time"
|
||||
yield
|
||||
icalendar.cal.types_factory.types_map.pop('X-SOMETIME')
|
||||
icalendar.cal.types_factory.types_map.pop("X-SOMETIME")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def factory():
|
||||
"""Return a new component factory."""
|
||||
return icalendar.ComponentFactory()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def vUTCOffset_ignore_exceptions():
|
||||
icalendar.vUTCOffset.ignore_exceptions = True
|
||||
yield
|
||||
icalendar.vUTCOffset.ignore_exceptions = False
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def event_component(tzp):
|
||||
"""Return an event component."""
|
||||
c = Component()
|
||||
c.name = 'VEVENT'
|
||||
c.name = "VEVENT"
|
||||
return c
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def c(tzp):
|
||||
"""Return an empty component."""
|
||||
c = Component()
|
||||
return c
|
||||
|
||||
|
||||
comp = c
|
||||
|
||||
@pytest.fixture()
|
||||
|
||||
@pytest.fixture
|
||||
def calendar_component(tzp):
|
||||
"""Return an empty component."""
|
||||
c = Component()
|
||||
c.name = 'VCALENDAR'
|
||||
c.name = "VCALENDAR"
|
||||
return c
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def filled_event_component(c, calendar_component):
|
||||
"""Return an event with some values and add it to calendar_component."""
|
||||
e = Component(summary='A brief history of time')
|
||||
e.name = 'VEVENT'
|
||||
e.add('dtend', '20000102T000000', encode=0)
|
||||
e.add('dtstart', '20000101T000000', encode=0)
|
||||
e = Component(summary="A brief history of time")
|
||||
e.name = "VEVENT"
|
||||
e.add("dtend", "20000102T000000", encode=0)
|
||||
e.add("dtstart", "20000101T000000", encode=0)
|
||||
calendar_component.add_component(e)
|
||||
return e
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def calendar_with_resources(tzp):
|
||||
c = Calendar()
|
||||
c['resources'] = 'Chair, Table, "Room: 42"'
|
||||
c["resources"] = 'Chair, Table, "Room: 42"'
|
||||
return c
|
||||
|
||||
|
||||
|
|
@ -220,13 +249,13 @@ def other_tzp(request, tzp):
|
|||
return tzp
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def pytz_only(tzp):
|
||||
"""Skip tests that are not running under pytz."""
|
||||
assert tzp.uses_pytz()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def zoneinfo_only(tzp, request, tzp_name):
|
||||
"""Skip tests that are not running under zoneinfo."""
|
||||
assert tzp.uses_zoneinfo()
|
||||
|
|
@ -247,14 +276,18 @@ def pytest_generate_tests(metafunc):
|
|||
tzp_names = ["zoneinfo"]
|
||||
if "pytz_only" in metafunc.fixturenames:
|
||||
tzp_names = PYTZ_TZP
|
||||
assert not ("zoneinfo_only" in metafunc.fixturenames and "pytz_only" in metafunc.fixturenames), "Use pytz_only or zoneinfo_only but not both!"
|
||||
assert not (
|
||||
"zoneinfo_only" in metafunc.fixturenames
|
||||
and "pytz_only" in metafunc.fixturenames
|
||||
), "Use pytz_only or zoneinfo_only but not both!"
|
||||
metafunc.parametrize("tzp_name", tzp_names, scope="module")
|
||||
|
||||
|
||||
class DoctestZoneInfo(zoneinfo.ZoneInfo):
|
||||
"""Constent ZoneInfo representation for tests."""
|
||||
|
||||
def __repr__(self):
|
||||
return f"ZoneInfo(key={repr(self.key)})"
|
||||
return f"ZoneInfo(key={self.key!r})"
|
||||
|
||||
|
||||
def doctest_print(obj):
|
||||
|
|
@ -270,13 +303,13 @@ def doctest_import(name, *args, **kw):
|
|||
return pytz
|
||||
return __import__(name, *args, **kw)
|
||||
|
||||
@pytest.fixture()
|
||||
|
||||
@pytest.fixture
|
||||
def env_for_doctest(monkeypatch):
|
||||
"""Modify the environment to make doctests run."""
|
||||
monkeypatch.setitem(sys.modules, "zoneinfo", zoneinfo)
|
||||
monkeypatch.setattr(zoneinfo, "ZoneInfo", DoctestZoneInfo)
|
||||
from icalendar.timezone.zoneinfo import ZONEINFO
|
||||
|
||||
monkeypatch.setattr(ZONEINFO, "utc", zoneinfo.ZoneInfo("UTC"))
|
||||
return {
|
||||
"print": doctest_print
|
||||
}
|
||||
return {"print": doctest_print}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ These test cases reproduce the failure.
|
|||
Some more tests can be added to make sure that the behavior works properly.
|
||||
"""
|
||||
|
||||
def fuzz_calendar_v1(from_ical, calendar_string: str, multiple: bool, should_walk: bool):
|
||||
|
||||
def fuzz_calendar_v1(
|
||||
from_ical, calendar_string: str, multiple: bool, should_walk: bool
|
||||
):
|
||||
"""Take a from_ical function and reproduce the error.
|
||||
|
||||
The calendar_string is a fuzzed input.
|
||||
|
|
@ -16,7 +19,7 @@ def fuzz_calendar_v1(from_ical, calendar_string: str, multiple: bool, should_wal
|
|||
cal = [cal]
|
||||
for c in cal:
|
||||
if should_walk:
|
||||
for event in cal.walk('VEVENT'):
|
||||
for event in cal.walk("VEVENT"):
|
||||
event.to_ical()
|
||||
else:
|
||||
cal.to_ical()
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
"""This test tests all fuzzed calendars."""
|
||||
from icalendar.tests.fuzzed import fuzz_calendar_v1
|
||||
|
||||
import icalendar
|
||||
from icalendar.tests.fuzzed import fuzz_calendar_v1
|
||||
|
||||
|
||||
def test_fuzz_v1(fuzz_v1_calendar):
|
||||
"""Test a calendar."""
|
||||
with open(fuzz_v1_calendar, "rb") as f:
|
||||
fuzz_calendar_v1(
|
||||
icalendar.Calendar.from_ical,
|
||||
f.read(),
|
||||
multiple=True,
|
||||
should_walk=True
|
||||
icalendar.Calendar.from_ical, f.read(), multiple=True, should_walk=True
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,28 +1,24 @@
|
|||
import string
|
||||
import unittest
|
||||
|
||||
from hypothesis import given, settings
|
||||
import hypothesis.strategies as st
|
||||
from hypothesis import given, settings
|
||||
|
||||
from icalendar.parser import Contentline, Contentlines, Parameters
|
||||
import unittest
|
||||
|
||||
|
||||
def printable_characters(**kw):
|
||||
return st.text(
|
||||
st.characters(blacklist_categories=(
|
||||
'Cc', 'Cs'
|
||||
), **kw)
|
||||
)
|
||||
return st.text(st.characters(blacklist_categories=("Cc", "Cs"), **kw))
|
||||
|
||||
|
||||
key = st.text(string.ascii_letters + string.digits, min_size=1)
|
||||
value = printable_characters(blacklist_characters='\\;:\"')
|
||||
value = printable_characters(blacklist_characters='\\;:"')
|
||||
|
||||
|
||||
class TestFuzzing(unittest.TestCase):
|
||||
|
||||
@given(lines=st.lists(
|
||||
st.tuples(key, st.dictionaries(key, value), value),
|
||||
min_size=1
|
||||
))
|
||||
@given(
|
||||
lines=st.lists(st.tuples(key, st.dictionaries(key, value), value), min_size=1)
|
||||
)
|
||||
@settings(max_examples=10**3)
|
||||
def test_main(self, lines):
|
||||
cl = Contentlines()
|
||||
|
|
@ -33,6 +29,6 @@ class TestFuzzing(unittest.TestCase):
|
|||
# Happens when there is a random parameter 'self'...
|
||||
continue
|
||||
cl.append(Contentline.from_parts(key, params, value))
|
||||
cl.append('')
|
||||
cl.append("")
|
||||
|
||||
assert Contentlines.from_ical(cl.to_ical()) == cl
|
||||
|
|
|
|||
|
|
@ -1,47 +1,70 @@
|
|||
"""Test the identity and equality between properties."""
|
||||
|
||||
from datetime import date, datetime, time
|
||||
|
||||
from icalendar import vDDDTypes
|
||||
from datetime import datetime, date, time
|
||||
from icalendar.timezone.zoneinfo import zoneinfo
|
||||
|
||||
try:
|
||||
import pytz
|
||||
except ImportError:
|
||||
pytz = None
|
||||
from dateutil import tz
|
||||
import pytest
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
import pytest
|
||||
from dateutil import tz
|
||||
|
||||
vDDDTypes_list = [
|
||||
vDDDTypes(datetime(year=2022, month=7, day=22, hour=12, minute=7, tzinfo=zoneinfo.ZoneInfo("Europe/London"))),
|
||||
vDDDTypes(
|
||||
datetime(
|
||||
year=2022,
|
||||
month=7,
|
||||
day=22,
|
||||
hour=12,
|
||||
minute=7,
|
||||
tzinfo=zoneinfo.ZoneInfo("Europe/London"),
|
||||
)
|
||||
),
|
||||
vDDDTypes(datetime(year=2022, month=7, day=22, hour=12, minute=7)),
|
||||
vDDDTypes(datetime(year=2022, month=7, day=22, hour=12, minute=7, tzinfo=tz.UTC)),
|
||||
vDDDTypes(date(year=2022, month=7, day=22)),
|
||||
vDDDTypes(date(year=2022, month=7, day=23)),
|
||||
vDDDTypes(time(hour=22, minute=7, second=2))
|
||||
vDDDTypes(time(hour=22, minute=7, second=2)),
|
||||
]
|
||||
if pytz:
|
||||
vDDDTypes_list.append(vDDDTypes(pytz.timezone('EST').localize(datetime(year=2022, month=7, day=22, hour=12, minute=7))),)
|
||||
vDDDTypes_list.append(
|
||||
vDDDTypes(
|
||||
pytz.timezone("EST").localize(
|
||||
datetime(year=2022, month=7, day=22, hour=12, minute=7)
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def identity(x):
|
||||
return x
|
||||
|
||||
@pytest.mark.parametrize("map", [
|
||||
deepcopy,
|
||||
identity,
|
||||
hash,
|
||||
])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"map",
|
||||
[
|
||||
deepcopy,
|
||||
identity,
|
||||
hash,
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("v_type", vDDDTypes_list)
|
||||
@pytest.mark.parametrize("other", vDDDTypes_list)
|
||||
def test_vDDDTypes_equivalance(map, v_type, other):
|
||||
if v_type is other:
|
||||
assert map(v_type) == map(other), f"identity implies equality: {map.__name__}()"
|
||||
assert not (map(v_type) != map(other)), f"identity implies equality: {map.__name__}()"
|
||||
assert map(v_type) == map(other), f"identity implies equality: {map.__name__}()"
|
||||
else:
|
||||
assert map(v_type) != map(other), f"expected inequality: {map.__name__}()"
|
||||
assert not (map(v_type) == map(other)), f"expected inequality: {map.__name__}()"
|
||||
assert map(v_type) != map(other), f"expected inequality: {map.__name__}()"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("v_type", vDDDTypes_list)
|
||||
def test_inequality_with_different_types(v_type):
|
||||
assert v_type != 42
|
||||
assert v_type != 'test'
|
||||
assert v_type != "test"
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
"""Test that composed values are properly converted."""
|
||||
from icalendar import Event
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from icalendar import Event
|
||||
|
||||
|
||||
def test_vDDDLists_timezone(tzp):
|
||||
"""Test vDDDLists with timezone information."""
|
||||
vevent = Event()
|
||||
dt1 = tzp.localize(datetime(2013, 1, 1), 'Europe/Vienna')
|
||||
dt2 = tzp.localize(datetime(2013, 1, 2), 'Europe/Vienna')
|
||||
dt3 = tzp.localize(datetime(2013, 1, 3), 'Europe/Vienna')
|
||||
vevent.add('rdate', [dt1, dt2])
|
||||
vevent.add('exdate', dt3)
|
||||
dt1 = tzp.localize(datetime(2013, 1, 1), "Europe/Vienna")
|
||||
dt2 = tzp.localize(datetime(2013, 1, 2), "Europe/Vienna")
|
||||
dt3 = tzp.localize(datetime(2013, 1, 3), "Europe/Vienna")
|
||||
vevent.add("rdate", [dt1, dt2])
|
||||
vevent.add("exdate", dt3)
|
||||
ical = vevent.to_ical()
|
||||
|
||||
assert b'RDATE;TZID=Europe/Vienna:20130101T000000,20130102T000000' in ical
|
||||
assert b'EXDATE;TZID=Europe/Vienna:20130103T000000' in ical
|
||||
assert b"RDATE;TZID=Europe/Vienna:20130101T000000,20130102T000000" in ical
|
||||
assert b"EXDATE;TZID=Europe/Vienna:20130103T000000" in ical
|
||||
|
|
|
|||
|
|
@ -1,118 +1,112 @@
|
|||
from datetime import date
|
||||
from datetime import datetime
|
||||
from datetime import time
|
||||
from datetime import timedelta
|
||||
from icalendar.parser import Parameters
|
||||
import unittest
|
||||
from datetime import date, datetime, time, timedelta
|
||||
|
||||
from icalendar.parser import Parameters
|
||||
|
||||
|
||||
class TestProp(unittest.TestCase):
|
||||
|
||||
def test_prop_vFloat(self):
|
||||
from icalendar.prop import vFloat
|
||||
self.assertEqual(vFloat(1.0).to_ical(), b'1.0')
|
||||
self.assertEqual(vFloat.from_ical('42'), 42.0)
|
||||
self.assertEqual(vFloat(42).to_ical(), b'42.0')
|
||||
self.assertRaises(ValueError, vFloat.from_ical, '1s3')
|
||||
|
||||
self.assertEqual(vFloat(1.0).to_ical(), b"1.0")
|
||||
self.assertEqual(vFloat.from_ical("42"), 42.0)
|
||||
self.assertEqual(vFloat(42).to_ical(), b"42.0")
|
||||
self.assertRaises(ValueError, vFloat.from_ical, "1s3")
|
||||
|
||||
def test_prop_vInt(self):
|
||||
from icalendar.prop import vInt
|
||||
self.assertEqual(vInt(42).to_ical(), b'42')
|
||||
self.assertEqual(vInt.from_ical('13'), 13)
|
||||
self.assertRaises(ValueError, vInt.from_ical, '1s3')
|
||||
|
||||
self.assertEqual(vInt(42).to_ical(), b"42")
|
||||
self.assertEqual(vInt.from_ical("13"), 13)
|
||||
self.assertRaises(ValueError, vInt.from_ical, "1s3")
|
||||
|
||||
def test_prop_vDDDLists(self):
|
||||
from icalendar.prop import vDDDLists
|
||||
|
||||
dt_list = vDDDLists.from_ical('19960402T010000Z')
|
||||
dt_list = vDDDLists.from_ical("19960402T010000Z")
|
||||
self.assertTrue(isinstance(dt_list, list))
|
||||
self.assertEqual(len(dt_list), 1)
|
||||
self.assertTrue(isinstance(dt_list[0], datetime))
|
||||
self.assertEqual(str(dt_list[0]), '1996-04-02 01:00:00+00:00')
|
||||
self.assertEqual(str(dt_list[0]), "1996-04-02 01:00:00+00:00")
|
||||
|
||||
p = '19960402T010000Z,19960403T010000Z,19960404T010000Z'
|
||||
p = "19960402T010000Z,19960403T010000Z,19960404T010000Z"
|
||||
dt_list = vDDDLists.from_ical(p)
|
||||
self.assertEqual(len(dt_list), 3)
|
||||
self.assertEqual(str(dt_list[0]), '1996-04-02 01:00:00+00:00')
|
||||
self.assertEqual(str(dt_list[2]), '1996-04-04 01:00:00+00:00')
|
||||
self.assertEqual(str(dt_list[0]), "1996-04-02 01:00:00+00:00")
|
||||
self.assertEqual(str(dt_list[2]), "1996-04-04 01:00:00+00:00")
|
||||
|
||||
dt_list = vDDDLists([])
|
||||
self.assertEqual(dt_list.to_ical(), b'')
|
||||
self.assertEqual(dt_list.to_ical(), b"")
|
||||
|
||||
dt_list = vDDDLists([datetime(2000, 1, 1)])
|
||||
self.assertEqual(dt_list.to_ical(), b'20000101T000000')
|
||||
self.assertEqual(dt_list.to_ical(), b"20000101T000000")
|
||||
|
||||
dt_list = vDDDLists([datetime(2000, 1, 1), datetime(2000, 11, 11)])
|
||||
self.assertEqual(dt_list.to_ical(), b'20000101T000000,20001111T000000')
|
||||
|
||||
self.assertEqual(dt_list.to_ical(), b"20000101T000000,20001111T000000")
|
||||
|
||||
instance = vDDDLists([])
|
||||
self.assertFalse(instance == "value")
|
||||
|
||||
def test_prop_vDate(self):
|
||||
from icalendar.prop import vDate
|
||||
|
||||
self.assertEqual(vDate(date(2001, 1, 1)).to_ical(), b'20010101')
|
||||
self.assertEqual(vDate(date(1899, 1, 1)).to_ical(), b'18990101')
|
||||
self.assertEqual(vDate(date(2001, 1, 1)).to_ical(), b"20010101")
|
||||
self.assertEqual(vDate(date(1899, 1, 1)).to_ical(), b"18990101")
|
||||
|
||||
self.assertEqual(vDate.from_ical('20010102'), date(2001, 1, 2))
|
||||
self.assertEqual(vDate.from_ical("20010102"), date(2001, 1, 2))
|
||||
|
||||
self.assertRaises(ValueError, vDate, 'd')
|
||||
self.assertRaises(ValueError, vDate.from_ical, '200102')
|
||||
self.assertRaises(ValueError, vDate, "d")
|
||||
self.assertRaises(ValueError, vDate.from_ical, "200102")
|
||||
|
||||
def test_prop_vDuration(self):
|
||||
from icalendar.prop import vDuration
|
||||
|
||||
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, 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')
|
||||
self.assertEqual(vDuration(timedelta(0, 7384)).to_ical(), b'PT2H3M4S')
|
||||
self.assertEqual(vDuration(timedelta(0, 184)).to_ical(), b'PT3M4S')
|
||||
self.assertEqual(vDuration(timedelta(0, 22)).to_ical(), b'PT22S')
|
||||
self.assertEqual(vDuration(timedelta(0, 3622)).to_ical(), b'PT1H0M22S')
|
||||
self.assertEqual(vDuration(timedelta(days=1, hours=5)).to_ical(),
|
||||
b'P1DT5H')
|
||||
self.assertEqual(vDuration(timedelta(hours=-5)).to_ical(), b'-PT5H')
|
||||
self.assertEqual(vDuration(timedelta(days=-1, hours=-5)).to_ical(),
|
||||
b'-P1DT5H')
|
||||
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, 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")
|
||||
self.assertEqual(vDuration(timedelta(0, 7384)).to_ical(), b"PT2H3M4S")
|
||||
self.assertEqual(vDuration(timedelta(0, 184)).to_ical(), b"PT3M4S")
|
||||
self.assertEqual(vDuration(timedelta(0, 22)).to_ical(), b"PT22S")
|
||||
self.assertEqual(vDuration(timedelta(0, 3622)).to_ical(), b"PT1H0M22S")
|
||||
self.assertEqual(vDuration(timedelta(days=1, hours=5)).to_ical(), b"P1DT5H")
|
||||
self.assertEqual(vDuration(timedelta(hours=-5)).to_ical(), b"-PT5H")
|
||||
self.assertEqual(vDuration(timedelta(days=-1, hours=-5)).to_ical(), b"-P1DT5H")
|
||||
|
||||
# How does the parsing work?
|
||||
self.assertEqual(vDuration.from_ical('PT1H0M22S'), timedelta(0, 3622))
|
||||
self.assertEqual(vDuration.from_ical("PT1H0M22S"), timedelta(0, 3622))
|
||||
|
||||
self.assertRaises(ValueError, vDuration.from_ical, 'kox')
|
||||
self.assertRaises(ValueError, vDuration.from_ical, "kox")
|
||||
|
||||
self.assertEqual(vDuration.from_ical('-P14D'), timedelta(-14))
|
||||
self.assertEqual(vDuration.from_ical("-P14D"), timedelta(-14))
|
||||
|
||||
self.assertRaises(ValueError, vDuration, 11)
|
||||
|
||||
# calling to_ical twice should result in same output
|
||||
duration = vDuration(timedelta(days=-1, hours=-5))
|
||||
self.assertEqual(duration.to_ical(), b'-P1DT5H')
|
||||
self.assertEqual(duration.to_ical(), b'-P1DT5H')
|
||||
self.assertEqual(duration.to_ical(), b"-P1DT5H")
|
||||
self.assertEqual(duration.to_ical(), b"-P1DT5H")
|
||||
|
||||
def test_prop_vWeekday(self):
|
||||
from icalendar.prop import vWeekday
|
||||
|
||||
self.assertEqual(vWeekday('mo').to_ical(), b'MO')
|
||||
self.assertRaises(ValueError, vWeekday, 'erwer')
|
||||
self.assertEqual(vWeekday.from_ical('mo'), 'MO')
|
||||
self.assertEqual(vWeekday.from_ical('+3mo'), '+3MO')
|
||||
self.assertRaises(ValueError, vWeekday.from_ical, 'Saturday')
|
||||
self.assertEqual(vWeekday('+mo').to_ical(), b'+MO')
|
||||
self.assertEqual(vWeekday('+3mo').to_ical(), b'+3MO')
|
||||
self.assertEqual(vWeekday('-tu').to_ical(), b'-TU')
|
||||
self.assertEqual(vWeekday("mo").to_ical(), b"MO")
|
||||
self.assertRaises(ValueError, vWeekday, "erwer")
|
||||
self.assertEqual(vWeekday.from_ical("mo"), "MO")
|
||||
self.assertEqual(vWeekday.from_ical("+3mo"), "+3MO")
|
||||
self.assertRaises(ValueError, vWeekday.from_ical, "Saturday")
|
||||
self.assertEqual(vWeekday("+mo").to_ical(), b"+MO")
|
||||
self.assertEqual(vWeekday("+3mo").to_ical(), b"+3MO")
|
||||
self.assertEqual(vWeekday("-tu").to_ical(), b"-TU")
|
||||
|
||||
def test_prop_vFrequency(self):
|
||||
from icalendar.prop import vFrequency
|
||||
|
||||
self.assertRaises(ValueError, vFrequency, 'bad test')
|
||||
self.assertEqual(vFrequency('daily').to_ical(), b'DAILY')
|
||||
self.assertEqual(vFrequency('daily').from_ical('MONTHLY'), 'MONTHLY')
|
||||
self.assertRaises(ValueError, vFrequency, "bad test")
|
||||
self.assertEqual(vFrequency("daily").to_ical(), b"DAILY")
|
||||
self.assertEqual(vFrequency("daily").from_ical("MONTHLY"), "MONTHLY")
|
||||
self.assertRaises(ValueError, vFrequency.from_ical, 234)
|
||||
|
||||
def test_prop_vRecur(self):
|
||||
|
|
@ -121,123 +115,117 @@ class TestProp(unittest.TestCase):
|
|||
# Let's see how close we can get to one from the rfc:
|
||||
# FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30
|
||||
|
||||
r = dict({'freq': 'yearly', 'interval': 2})
|
||||
r.update({
|
||||
'bymonth': 1,
|
||||
'byday': 'su',
|
||||
'byhour': [8, 9],
|
||||
'byminute': 30
|
||||
})
|
||||
r = dict({"freq": "yearly", "interval": 2})
|
||||
r.update({"bymonth": 1, "byday": "su", "byhour": [8, 9], "byminute": 30})
|
||||
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;BYMONTH=1",
|
||||
)
|
||||
|
||||
r = vRecur(FREQ='yearly', INTERVAL=2)
|
||||
r.update({
|
||||
'BYMONTH': 1,
|
||||
'BYDAY': 'su',
|
||||
'BYHOUR': [8, 9],
|
||||
'BYMINUTE': 30,
|
||||
})
|
||||
r = vRecur(FREQ="yearly", INTERVAL=2)
|
||||
r.update(
|
||||
{
|
||||
"BYMONTH": 1,
|
||||
"BYDAY": "su",
|
||||
"BYHOUR": [8, 9],
|
||||
"BYMINUTE": 30,
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
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;BYMONTH=1",
|
||||
)
|
||||
|
||||
r = vRecur(freq='DAILY', count=10)
|
||||
r['bysecond'] = [0, 15, 30, 45]
|
||||
self.assertEqual(r.to_ical(),
|
||||
b'FREQ=DAILY;COUNT=10;BYSECOND=0,15,30,45')
|
||||
r = vRecur(freq="DAILY", count=10)
|
||||
r["bysecond"] = [0, 15, 30, 45]
|
||||
self.assertEqual(r.to_ical(), b"FREQ=DAILY;COUNT=10;BYSECOND=0,15,30,45")
|
||||
|
||||
r = vRecur(freq='DAILY', until=datetime(2005, 1, 1, 12, 0, 0))
|
||||
self.assertEqual(r.to_ical(), b'FREQ=DAILY;UNTIL=20050101T120000')
|
||||
r = vRecur(freq="DAILY", until=datetime(2005, 1, 1, 12, 0, 0))
|
||||
self.assertEqual(r.to_ical(), b"FREQ=DAILY;UNTIL=20050101T120000")
|
||||
|
||||
# How do we fare with regards to parsing?
|
||||
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'
|
||||
)
|
||||
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")
|
||||
|
||||
r = vRecur.from_ical('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=-SU;'
|
||||
'BYHOUR=8,9;BYMINUTE=30')
|
||||
r = vRecur.from_ical(
|
||||
"FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=-SU;" "BYHOUR=8,9;BYMINUTE=30"
|
||||
)
|
||||
self.assertEqual(
|
||||
r,
|
||||
{'BYHOUR': [8, 9], 'BYDAY': ['-SU'], 'BYMINUTE': [30],
|
||||
'BYMONTH': [1], 'FREQ': ['YEARLY'], 'INTERVAL': [2]}
|
||||
{
|
||||
"BYHOUR": [8, 9],
|
||||
"BYDAY": ["-SU"],
|
||||
"BYMINUTE": [30],
|
||||
"BYMONTH": [1],
|
||||
"FREQ": ["YEARLY"],
|
||||
"INTERVAL": [2],
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
vRecur(r).to_ical(),
|
||||
b'FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=-SU;'
|
||||
b'BYMONTH=1'
|
||||
b"FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=-SU;" b"BYMONTH=1",
|
||||
)
|
||||
|
||||
r = vRecur.from_ical('FREQ=WEEKLY;INTERVAL=1;BYWEEKDAY=TH')
|
||||
r = vRecur.from_ical("FREQ=WEEKLY;INTERVAL=1;BYWEEKDAY=TH")
|
||||
|
||||
self.assertEqual(
|
||||
r,
|
||||
{'FREQ': ['WEEKLY'], 'INTERVAL': [1], 'BYWEEKDAY': ['TH']}
|
||||
)
|
||||
self.assertEqual(r, {"FREQ": ["WEEKLY"], "INTERVAL": [1], "BYWEEKDAY": ["TH"]})
|
||||
|
||||
self.assertEqual(
|
||||
vRecur(r).to_ical(),
|
||||
b'FREQ=WEEKLY;INTERVAL=1;BYWEEKDAY=TH'
|
||||
)
|
||||
self.assertEqual(vRecur(r).to_ical(), b"FREQ=WEEKLY;INTERVAL=1;BYWEEKDAY=TH")
|
||||
|
||||
# Some examples from the spec
|
||||
r = vRecur.from_ical('FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1')
|
||||
self.assertEqual(vRecur(r).to_ical(),
|
||||
b'FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1')
|
||||
r = vRecur.from_ical("FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1")
|
||||
self.assertEqual(
|
||||
vRecur(r).to_ical(), b"FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1"
|
||||
)
|
||||
|
||||
p = 'FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30'
|
||||
p = "FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30"
|
||||
r = vRecur.from_ical(p)
|
||||
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;BYMONTH=1",
|
||||
)
|
||||
|
||||
# and some errors
|
||||
self.assertRaises(ValueError, vRecur.from_ical, 'BYDAY=12')
|
||||
self.assertRaises(ValueError, vRecur.from_ical, "BYDAY=12")
|
||||
|
||||
# when key is not RFC-compliant, parse it as vText
|
||||
r = vRecur.from_ical('FREQ=MONTHLY;BYOTHER=TEXT;BYEASTER=-3')
|
||||
self.assertEqual(vRecur(r).to_ical(),
|
||||
b'FREQ=MONTHLY;BYEASTER=-3;BYOTHER=TEXT')
|
||||
r = vRecur.from_ical("FREQ=MONTHLY;BYOTHER=TEXT;BYEASTER=-3")
|
||||
self.assertEqual(vRecur(r).to_ical(), b"FREQ=MONTHLY;BYEASTER=-3;BYOTHER=TEXT")
|
||||
|
||||
def test_prop_vText(self):
|
||||
from icalendar.prop import vText
|
||||
|
||||
self.assertEqual(vText('Simple text').to_ical(), b'Simple text')
|
||||
self.assertEqual(vText("Simple text").to_ical(), b"Simple text")
|
||||
|
||||
# Escaped text
|
||||
t = vText('Text ; with escaped, chars')
|
||||
self.assertEqual(t.to_ical(), b'Text \\; with escaped\\, chars')
|
||||
t = vText("Text ; with escaped, chars")
|
||||
self.assertEqual(t.to_ical(), b"Text \\; with escaped\\, chars")
|
||||
|
||||
# Escaped newlines
|
||||
self.assertEqual(vText('Text with escaped\\N chars').to_ical(),
|
||||
b'Text with escaped\\n chars')
|
||||
self.assertEqual(
|
||||
vText("Text with escaped\\N chars").to_ical(), b"Text with escaped\\n chars"
|
||||
)
|
||||
|
||||
# If you pass a unicode object, it will be utf-8 encoded. As this is
|
||||
# the (only) standard that RFC 5545 support.
|
||||
t = vText('international chars \xe4\xf6\xfc')
|
||||
self.assertEqual(t.to_ical(),
|
||||
b'international chars \xc3\xa4\xc3\xb6\xc3\xbc')
|
||||
t = vText("international chars \xe4\xf6\xfc")
|
||||
self.assertEqual(t.to_ical(), b"international chars \xc3\xa4\xc3\xb6\xc3\xbc")
|
||||
|
||||
# and parsing?
|
||||
self.assertEqual(vText.from_ical('Text \\; with escaped\\, chars'),
|
||||
'Text ; with escaped, chars')
|
||||
self.assertEqual(
|
||||
vText.from_ical("Text \\; with escaped\\, chars"),
|
||||
"Text ; with escaped, chars",
|
||||
)
|
||||
|
||||
t = vText.from_ical('A string with\\; some\\\\ characters in\\it')
|
||||
t = vText.from_ical("A string with\\; some\\\\ characters in\\it")
|
||||
self.assertEqual(t, "A string with; some\\ characters in\\it")
|
||||
|
||||
# We are forgiving to utf-8 encoding errors:
|
||||
# We intentionally use a string with unexpected encoding
|
||||
#
|
||||
self.assertEqual(vText.from_ical(b'Ol\xe9'), 'Ol\ufffd')
|
||||
self.assertEqual(vText.from_ical(b"Ol\xe9"), "Ol\ufffd")
|
||||
|
||||
# Notice how accented E character, encoded with latin-1, got replaced
|
||||
# with the official U+FFFD REPLACEMENT CHARACTER.
|
||||
|
|
@ -245,100 +233,96 @@ class TestProp(unittest.TestCase):
|
|||
def test_prop_vTime(self):
|
||||
from icalendar.prop import vTime
|
||||
|
||||
self.assertEqual(vTime(12, 30, 0).to_ical(), '123000')
|
||||
self.assertEqual(vTime.from_ical('123000'), time(12, 30))
|
||||
self.assertEqual(vTime(12, 30, 0).to_ical(), "123000")
|
||||
self.assertEqual(vTime.from_ical("123000"), time(12, 30))
|
||||
|
||||
# We should also fail, right?
|
||||
self.assertRaises(ValueError, vTime.from_ical, '263000')
|
||||
self.assertRaises(ValueError, vTime.from_ical, "263000")
|
||||
|
||||
self.assertRaises(ValueError, vTime, '263000')
|
||||
self.assertRaises(ValueError, vTime, "263000")
|
||||
|
||||
def test_prop_vUri(self):
|
||||
from icalendar.prop import vUri
|
||||
|
||||
self.assertEqual(vUri('http://www.example.com/').to_ical(),
|
||||
b'http://www.example.com/')
|
||||
self.assertEqual(vUri.from_ical('http://www.example.com/'),
|
||||
'http://www.example.com/')
|
||||
self.assertEqual(
|
||||
vUri("http://www.example.com/").to_ical(), b"http://www.example.com/"
|
||||
)
|
||||
self.assertEqual(
|
||||
vUri.from_ical("http://www.example.com/"), "http://www.example.com/"
|
||||
)
|
||||
|
||||
def test_prop_vGeo(self):
|
||||
from icalendar.prop import vGeo
|
||||
|
||||
# Pass a list
|
||||
self.assertEqual(vGeo([1.2, 3.0]).to_ical(), '1.2;3.0')
|
||||
self.assertEqual(vGeo([1.2, 3.0]).to_ical(), "1.2;3.0")
|
||||
|
||||
# Pass a tuple
|
||||
self.assertEqual(vGeo((1.2, 3.0)).to_ical(), '1.2;3.0')
|
||||
self.assertEqual(vGeo((1.2, 3.0)).to_ical(), "1.2;3.0")
|
||||
|
||||
g = vGeo.from_ical('37.386013;-122.082932')
|
||||
self.assertEqual(g, (float('37.386013'), float('-122.082932')))
|
||||
g = vGeo.from_ical("37.386013;-122.082932")
|
||||
self.assertEqual(g, (float("37.386013"), float("-122.082932")))
|
||||
|
||||
self.assertEqual(vGeo(g).to_ical(), '37.386013;-122.082932')
|
||||
self.assertEqual(vGeo(g).to_ical(), "37.386013;-122.082932")
|
||||
|
||||
self.assertRaises(ValueError, vGeo, 'g')
|
||||
self.assertRaises(ValueError, vGeo.from_ical, '1s3;1s3')
|
||||
self.assertRaises(ValueError, vGeo, "g")
|
||||
self.assertRaises(ValueError, vGeo.from_ical, "1s3;1s3")
|
||||
|
||||
def test_prop_vUTCOffset(self):
|
||||
from icalendar.prop import vUTCOffset
|
||||
|
||||
self.assertEqual(vUTCOffset(timedelta(hours=2)).to_ical(), '+0200')
|
||||
self.assertEqual(vUTCOffset(timedelta(hours=2)).to_ical(), "+0200")
|
||||
|
||||
self.assertEqual(vUTCOffset(timedelta(hours=-5)).to_ical(), '-0500')
|
||||
self.assertEqual(vUTCOffset(timedelta(hours=-5)).to_ical(), "-0500")
|
||||
|
||||
self.assertEqual(vUTCOffset(timedelta()).to_ical(), '+0000')
|
||||
self.assertEqual(vUTCOffset(timedelta()).to_ical(), "+0000")
|
||||
|
||||
self.assertEqual(vUTCOffset(timedelta(minutes=-30)).to_ical(),
|
||||
'-0030')
|
||||
self.assertEqual(vUTCOffset(timedelta(minutes=-30)).to_ical(), "-0030")
|
||||
|
||||
self.assertEqual(
|
||||
vUTCOffset(timedelta(hours=2, minutes=-30)).to_ical(),
|
||||
'+0130'
|
||||
)
|
||||
self.assertEqual(vUTCOffset(timedelta(hours=2, minutes=-30)).to_ical(), "+0130")
|
||||
|
||||
self.assertEqual(vUTCOffset(timedelta(hours=1, minutes=30)).to_ical(),
|
||||
'+0130')
|
||||
self.assertEqual(vUTCOffset(timedelta(hours=1, minutes=30)).to_ical(), "+0130")
|
||||
|
||||
# Support seconds
|
||||
self.assertEqual(vUTCOffset(timedelta(hours=1,
|
||||
minutes=30,
|
||||
seconds=7)).to_ical(), '+013007')
|
||||
self.assertEqual(
|
||||
vUTCOffset(timedelta(hours=1, minutes=30, seconds=7)).to_ical(), "+013007"
|
||||
)
|
||||
|
||||
# Parsing
|
||||
|
||||
self.assertEqual(vUTCOffset.from_ical('0000'), timedelta(0))
|
||||
self.assertEqual(vUTCOffset.from_ical('-0030'), timedelta(-1, 84600))
|
||||
self.assertEqual(vUTCOffset.from_ical('+0200'), timedelta(0, 7200))
|
||||
self.assertEqual(vUTCOffset.from_ical('+023040'), timedelta(0, 9040))
|
||||
self.assertEqual(vUTCOffset.from_ical("0000"), timedelta(0))
|
||||
self.assertEqual(vUTCOffset.from_ical("-0030"), timedelta(-1, 84600))
|
||||
self.assertEqual(vUTCOffset.from_ical("+0200"), timedelta(0, 7200))
|
||||
self.assertEqual(vUTCOffset.from_ical("+023040"), timedelta(0, 9040))
|
||||
|
||||
self.assertEqual(vUTCOffset(vUTCOffset.from_ical('+0230')).to_ical(),
|
||||
'+0230')
|
||||
self.assertEqual(vUTCOffset(vUTCOffset.from_ical("+0230")).to_ical(), "+0230")
|
||||
|
||||
# And a few failures
|
||||
self.assertRaises(ValueError, vUTCOffset.from_ical, '+323k')
|
||||
self.assertRaises(ValueError, vUTCOffset.from_ical, "+323k")
|
||||
|
||||
self.assertRaises(ValueError, vUTCOffset.from_ical, '+2400')
|
||||
self.assertRaises(ValueError, vUTCOffset.from_ical, "+2400")
|
||||
|
||||
self.assertRaises(ValueError, vUTCOffset, '0:00:00')
|
||||
self.assertRaises(ValueError, vUTCOffset, "0:00:00")
|
||||
|
||||
def test_prop_vInline(self):
|
||||
from icalendar.prop import vInline
|
||||
|
||||
self.assertEqual(vInline('Some text'), 'Some text')
|
||||
self.assertEqual(vInline('Some text').to_ical(), b'Some text')
|
||||
self.assertEqual(vInline.from_ical('Some text'), 'Some text')
|
||||
self.assertEqual(vInline("Some text"), "Some text")
|
||||
self.assertEqual(vInline("Some text").to_ical(), b"Some text")
|
||||
self.assertEqual(vInline.from_ical("Some text"), "Some text")
|
||||
|
||||
t2 = vInline('other text')
|
||||
t2.params['cn'] = 'Test Osterone'
|
||||
t2 = vInline("other text")
|
||||
t2.params["cn"] = "Test Osterone"
|
||||
self.assertIsInstance(t2.params, Parameters)
|
||||
self.assertEqual(t2.params, {'CN': 'Test Osterone'})
|
||||
self.assertEqual(t2.params, {"CN": "Test Osterone"})
|
||||
|
||||
def test_prop_vCategory(self):
|
||||
from icalendar.prop import vCategory
|
||||
|
||||
catz = ['cat 1', 'cat 2', 'cat 3']
|
||||
catz = ["cat 1", "cat 2", "cat 3"]
|
||||
v_cat = vCategory(catz)
|
||||
|
||||
self.assertEqual(v_cat.to_ical(), b'cat 1,cat 2,cat 3')
|
||||
self.assertEqual(v_cat.to_ical(), b"cat 1,cat 2,cat 3")
|
||||
self.assertEqual(vCategory.from_ical(v_cat.to_ical()), catz)
|
||||
c = vCategory(vCategory.from_ical("APPOINTMENT,EDUCATION"))
|
||||
cats = list(c)
|
||||
|
|
@ -349,27 +333,31 @@ class TestProp(unittest.TestCase):
|
|||
|
||||
# To get a type you can use it like this.
|
||||
factory = TypesFactory()
|
||||
datetime_parser = factory['date-time']
|
||||
self.assertEqual(datetime_parser(datetime(2001, 1, 1)).to_ical(),
|
||||
b'20010101T000000')
|
||||
datetime_parser = factory["date-time"]
|
||||
self.assertEqual(
|
||||
datetime_parser(datetime(2001, 1, 1)).to_ical(), b"20010101T000000"
|
||||
)
|
||||
|
||||
# A typical use is when the parser tries to find a content type and use
|
||||
# text as the default
|
||||
value = '20050101T123000'
|
||||
value_type = 'date-time'
|
||||
self.assertEqual(factory.get(value_type, 'text').from_ical(value),
|
||||
datetime(2005, 1, 1, 12, 30))
|
||||
value = "20050101T123000"
|
||||
value_type = "date-time"
|
||||
self.assertEqual(
|
||||
factory.get(value_type, "text").from_ical(value),
|
||||
datetime(2005, 1, 1, 12, 30),
|
||||
)
|
||||
|
||||
# It can also be used to directly encode property and parameter values
|
||||
self.assertEqual(
|
||||
factory.to_ical('comment', 'by Rasmussen, Max M\xfcller'),
|
||||
b'by Rasmussen\\, Max M\xc3\xbcller'
|
||||
factory.to_ical("comment", "by Rasmussen, Max M\xfcller"),
|
||||
b"by Rasmussen\\, Max M\xc3\xbcller",
|
||||
)
|
||||
self.assertEqual(factory.to_ical('priority', 1), b'1')
|
||||
self.assertEqual(factory.to_ical('cn', 'Rasmussen, Max M\xfcller'),
|
||||
b'Rasmussen\\, Max M\xc3\xbcller')
|
||||
self.assertEqual(factory.to_ical("priority", 1), b"1")
|
||||
self.assertEqual(
|
||||
factory.from_ical('cn', b'Rasmussen\\, Max M\xc3\xb8ller'),
|
||||
'Rasmussen, Max M\xf8ller'
|
||||
factory.to_ical("cn", "Rasmussen, Max M\xfcller"),
|
||||
b"Rasmussen\\, Max M\xc3\xbcller",
|
||||
)
|
||||
self.assertEqual(
|
||||
factory.from_ical("cn", b"Rasmussen\\, Max M\xc3\xb8ller"),
|
||||
"Rasmussen, Max M\xf8ller",
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Test vBinary"""
|
||||
|
||||
import pytest
|
||||
|
||||
from icalendar import vBinary
|
||||
|
|
@ -6,36 +7,39 @@ from icalendar.parser import Parameters
|
|||
|
||||
|
||||
def test_text():
|
||||
txt = b'This is gibberish'
|
||||
txt_ical = b'VGhpcyBpcyBnaWJiZXJpc2g='
|
||||
assert (vBinary(txt).to_ical() == txt_ical)
|
||||
assert (vBinary.from_ical(txt_ical) == txt)
|
||||
txt = b"This is gibberish"
|
||||
txt_ical = b"VGhpcyBpcyBnaWJiZXJpc2g="
|
||||
assert vBinary(txt).to_ical() == txt_ical
|
||||
assert vBinary.from_ical(txt_ical) == txt
|
||||
|
||||
|
||||
def test_binary():
|
||||
txt = b'Binary data \x13 \x56'
|
||||
txt_ical = b'QmluYXJ5IGRhdGEgEyBW'
|
||||
assert (vBinary(txt).to_ical() == txt_ical)
|
||||
assert (vBinary.from_ical(txt_ical) == txt)
|
||||
txt = b"Binary data \x13 \x56"
|
||||
txt_ical = b"QmluYXJ5IGRhdGEgEyBW"
|
||||
assert vBinary(txt).to_ical() == txt_ical
|
||||
assert vBinary.from_ical(txt_ical) == txt
|
||||
|
||||
|
||||
def test_param():
|
||||
assert isinstance(vBinary('txt').params, Parameters)
|
||||
assert (
|
||||
vBinary('txt').params == {'VALUE': 'BINARY', 'ENCODING': 'BASE64'}
|
||||
)
|
||||
assert isinstance(vBinary("txt").params, Parameters)
|
||||
assert vBinary("txt").params == {"VALUE": "BINARY", "ENCODING": "BASE64"}
|
||||
|
||||
|
||||
def test_long_data():
|
||||
"""Long data should not have line breaks, as that would interfere"""
|
||||
txt = b'a' * 99
|
||||
txt_ical = b'YWFh' * 33
|
||||
assert (vBinary(txt).to_ical() == txt_ical)
|
||||
assert (vBinary.from_ical(txt_ical) == txt)
|
||||
|
||||
txt = b"a" * 99
|
||||
txt_ical = b"YWFh" * 33
|
||||
assert vBinary(txt).to_ical() == txt_ical
|
||||
assert vBinary.from_ical(txt_ical) == txt
|
||||
|
||||
|
||||
def test_repr():
|
||||
instance = vBinary("value")
|
||||
assert repr(instance) == "vBinary(b'dmFsdWU=')"
|
||||
|
||||
|
||||
|
||||
def test_from_ical():
|
||||
with pytest.raises(ValueError, match='Not valid base 64 encoding.'):
|
||||
with pytest.raises(ValueError, match="Not valid base 64 encoding."):
|
||||
vBinary.from_ical("value")
|
||||
with pytest.raises(ValueError, match='Not valid base 64 encoding.'):
|
||||
vBinary.from_ical("áèਮ")
|
||||
with pytest.raises(ValueError, match="Not valid base 64 encoding."):
|
||||
vBinary.from_ical("áèਮ")
|
||||
|
|
|
|||
|
|
@ -1,17 +1,22 @@
|
|||
from icalendar.prop import vBoolean
|
||||
import pytest
|
||||
|
||||
from icalendar.prop import vBoolean
|
||||
|
||||
|
||||
def test_true():
|
||||
assert (vBoolean(True).to_ical() == b'TRUE')
|
||||
assert vBoolean(True).to_ical() == b"TRUE"
|
||||
|
||||
|
||||
def test_false():
|
||||
assert (vBoolean(0).to_ical() == b'FALSE')
|
||||
assert vBoolean(0).to_ical() == b"FALSE"
|
||||
|
||||
|
||||
def test_roundtrip():
|
||||
assert (vBoolean.from_ical(vBoolean(True).to_ical()) == True)
|
||||
assert (vBoolean.from_ical('true') == True)
|
||||
assert vBoolean.from_ical(vBoolean(True).to_ical()) == True
|
||||
assert vBoolean.from_ical("true") == True
|
||||
|
||||
|
||||
def test_error():
|
||||
"""Error: key not exists"""
|
||||
with pytest.raises(ValueError):
|
||||
vBoolean.from_ical('ture')
|
||||
vBoolean.from_ical("ture")
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
from icalendar.prop import vCalAddress
|
||||
from icalendar.parser import Parameters
|
||||
from icalendar.prop import vCalAddress
|
||||
|
||||
txt = b'MAILTO:maxm@mxm.dk'
|
||||
txt = b"MAILTO:maxm@mxm.dk"
|
||||
a = vCalAddress(txt)
|
||||
a.params['cn'] = 'Max M'
|
||||
a.params["cn"] = "Max M"
|
||||
|
||||
|
||||
def test_to_ical():
|
||||
|
|
@ -11,14 +11,14 @@ def test_to_ical():
|
|||
|
||||
|
||||
def test_params():
|
||||
assert isinstance(a.params, Parameters)
|
||||
assert a.params == {'CN': 'Max M'}
|
||||
assert isinstance(a.params, Parameters)
|
||||
assert a.params == {"CN": "Max M"}
|
||||
|
||||
|
||||
def test_from_ical():
|
||||
assert vCalAddress.from_ical(txt) == 'MAILTO:maxm@mxm.dk'
|
||||
|
||||
assert vCalAddress.from_ical(txt) == "MAILTO:maxm@mxm.dk"
|
||||
|
||||
|
||||
def test_repr():
|
||||
instance = vCalAddress("value")
|
||||
assert repr(instance) == "vCalAddress('value')"
|
||||
assert repr(instance) == "vCalAddress('value')"
|
||||
|
|
|
|||
|
|
@ -1,30 +1,35 @@
|
|||
from icalendar.prop import vDDDTypes
|
||||
from datetime import date, datetime, timedelta, time
|
||||
from datetime import date, datetime, time, timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
from icalendar.prop import vDDDTypes
|
||||
|
||||
|
||||
def test_instance():
|
||||
assert isinstance(vDDDTypes.from_ical('20010101T123000'), datetime)
|
||||
assert isinstance(vDDDTypes.from_ical('20010101'), date)
|
||||
assert isinstance(vDDDTypes.from_ical("20010101T123000"), datetime)
|
||||
assert isinstance(vDDDTypes.from_ical("20010101"), date)
|
||||
|
||||
|
||||
def test_datetime_with_timezone(tzp):
|
||||
assert vDDDTypes.from_ical('20010101T123000Z') == \
|
||||
tzp.localize_utc(datetime(2001, 1, 1, 12, 30))
|
||||
assert vDDDTypes.from_ical("20010101T123000Z") == tzp.localize_utc(
|
||||
datetime(2001, 1, 1, 12, 30)
|
||||
)
|
||||
|
||||
|
||||
def test_timedelta():
|
||||
assert vDDDTypes.from_ical('P31D') == timedelta(31)
|
||||
assert vDDDTypes.from_ical('-P31D') == timedelta(-31)
|
||||
assert vDDDTypes.from_ical("P31D") == timedelta(31)
|
||||
assert vDDDTypes.from_ical("-P31D") == timedelta(-31)
|
||||
|
||||
|
||||
def test_bad_input():
|
||||
with pytest.raises(ValueError):
|
||||
vDDDTypes(42)
|
||||
|
||||
|
||||
def test_time_from_string():
|
||||
assert vDDDTypes.from_ical('123000') == time(12, 30)
|
||||
assert isinstance(vDDDTypes.from_ical('123000'), time)
|
||||
assert vDDDTypes.from_ical("123000") == time(12, 30)
|
||||
assert isinstance(vDDDTypes.from_ical("123000"), time)
|
||||
|
||||
|
||||
def test_invalid_period_to_ical():
|
||||
invalid_period = (datetime(2000, 1, 1), datetime(2000, 1, 2), datetime(2000, 1, 2))
|
||||
|
|
|
|||
|
|
@ -1,42 +1,47 @@
|
|||
from icalendar.prop import vDatetime
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from icalendar.prop import vDatetime
|
||||
|
||||
|
||||
def test_to_ical():
|
||||
assert vDatetime(datetime(2001, 1, 1, 12, 30, 0)).to_ical() == b'20010101T123000'
|
||||
assert vDatetime(datetime(2001, 1, 1, 12, 30, 0)).to_ical() == b"20010101T123000"
|
||||
|
||||
|
||||
def test_from_ical():
|
||||
assert vDatetime.from_ical('20000101T120000') == datetime(2000, 1, 1, 12, 0)
|
||||
assert vDatetime.from_ical('20010101T000000') == datetime(2001, 1, 1, 0, 0)
|
||||
assert vDatetime.from_ical("20000101T120000") == datetime(2000, 1, 1, 12, 0)
|
||||
assert vDatetime.from_ical("20010101T000000") == datetime(2001, 1, 1, 0, 0)
|
||||
|
||||
|
||||
def test_to_ical_utc(tzp):
|
||||
dutc = tzp.localize_utc(datetime(2001, 1, 1, 12, 30, 0))
|
||||
assert vDatetime(dutc).to_ical() == b'20010101T123000Z'
|
||||
assert vDatetime(dutc).to_ical() == b"20010101T123000Z"
|
||||
|
||||
|
||||
def test_to_ical_utc_1899(tzp):
|
||||
dutc = tzp.localize_utc(datetime(1899, 1, 1, 12, 30, 0))
|
||||
assert vDatetime(dutc).to_ical() == b'18990101T123000Z'
|
||||
assert vDatetime(dutc).to_ical() == b"18990101T123000Z"
|
||||
|
||||
|
||||
def test_bad_ical():
|
||||
with pytest.raises(ValueError):
|
||||
vDatetime.from_ical('20010101T000000A')
|
||||
vDatetime.from_ical("20010101T000000A")
|
||||
|
||||
|
||||
def test_roundtrip():
|
||||
utc = vDatetime.from_ical('20010101T000000Z')
|
||||
assert vDatetime(utc).to_ical() == b'20010101T000000Z'
|
||||
utc = vDatetime.from_ical("20010101T000000Z")
|
||||
assert vDatetime(utc).to_ical() == b"20010101T000000Z"
|
||||
|
||||
|
||||
def test_transition(tzp):
|
||||
# 1 minute before transition to DST
|
||||
dat = vDatetime.from_ical('20120311T015959', 'America/Denver')
|
||||
assert dat.strftime('%Y%m%d%H%M%S %z') =='20120311015959 -0700'
|
||||
dat = vDatetime.from_ical("20120311T015959", "America/Denver")
|
||||
assert dat.strftime("%Y%m%d%H%M%S %z") == "20120311015959 -0700"
|
||||
|
||||
# After transition to DST
|
||||
dat = vDatetime.from_ical('20120311T030000', 'America/Denver')
|
||||
assert dat.strftime('%Y%m%d%H%M%S %z') == '20120311030000 -0600'
|
||||
dat = vDatetime.from_ical("20120311T030000", "America/Denver")
|
||||
assert dat.strftime("%Y%m%d%H%M%S %z") == "20120311030000 -0600"
|
||||
|
||||
dat = vDatetime.from_ical('20101010T000000', 'Europe/Vienna')
|
||||
assert vDatetime(dat).to_ical() == b'20101010T000000'
|
||||
dat = vDatetime.from_ical("20101010T000000", "Europe/Vienna")
|
||||
assert vDatetime(dat).to_ical() == b"20101010T000000"
|
||||
|
|
|
|||
|
|
@ -1,63 +1,61 @@
|
|||
import unittest
|
||||
from icalendar.prop import vPeriod
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
from icalendar.prop import vPeriod
|
||||
|
||||
|
||||
class TestProp(unittest.TestCase):
|
||||
|
||||
def test_one_day(self):
|
||||
# One day in exact datetimes
|
||||
per = (datetime(2000, 1, 1), datetime(2000, 1, 2))
|
||||
self.assertEqual(vPeriod(per).to_ical(),
|
||||
b'20000101T000000/20000102T000000')
|
||||
self.assertEqual(vPeriod(per).to_ical(), b"20000101T000000/20000102T000000")
|
||||
|
||||
per = (datetime(2000, 1, 1), timedelta(days=31))
|
||||
self.assertEqual(vPeriod(per).to_ical(), b'20000101T000000/P31D')
|
||||
self.assertEqual(vPeriod(per).to_ical(), b"20000101T000000/P31D")
|
||||
|
||||
def test_roundtrip(self):
|
||||
p = vPeriod.from_ical('20000101T000000/20000102T000000')
|
||||
self.assertEqual(
|
||||
p,
|
||||
(datetime(2000, 1, 1, 0, 0), datetime(2000, 1, 2, 0, 0))
|
||||
)
|
||||
self.assertEqual(vPeriod(p).to_ical(),
|
||||
b'20000101T000000/20000102T000000')
|
||||
p = vPeriod.from_ical("20000101T000000/20000102T000000")
|
||||
self.assertEqual(p, (datetime(2000, 1, 1, 0, 0), datetime(2000, 1, 2, 0, 0)))
|
||||
self.assertEqual(vPeriod(p).to_ical(), b"20000101T000000/20000102T000000")
|
||||
|
||||
self.assertEqual(vPeriod.from_ical('20000101T000000/P31D'),
|
||||
(datetime(2000, 1, 1, 0, 0), timedelta(31)))
|
||||
self.assertEqual(
|
||||
vPeriod.from_ical("20000101T000000/P31D"),
|
||||
(datetime(2000, 1, 1, 0, 0), timedelta(31)),
|
||||
)
|
||||
|
||||
def test_round_trip_with_absolute_time(self):
|
||||
p = vPeriod.from_ical('20000101T000000Z/20000102T000000Z')
|
||||
self.assertEqual(vPeriod(p).to_ical(),
|
||||
b'20000101T000000Z/20000102T000000Z')
|
||||
p = vPeriod.from_ical("20000101T000000Z/20000102T000000Z")
|
||||
self.assertEqual(vPeriod(p).to_ical(), b"20000101T000000Z/20000102T000000Z")
|
||||
|
||||
def test_bad_input(self):
|
||||
self.assertRaises(ValueError,
|
||||
vPeriod.from_ical, '20000101T000000/Psd31D')
|
||||
self.assertRaises(ValueError, vPeriod.from_ical, "20000101T000000/Psd31D")
|
||||
|
||||
|
||||
def test_timezoned(tzp):
|
||||
start = tzp.localize(datetime(2000, 1, 1), 'Europe/Copenhagen')
|
||||
end = tzp.localize(datetime(2000, 1, 2), 'Europe/Copenhagen')
|
||||
start = tzp.localize(datetime(2000, 1, 1), "Europe/Copenhagen")
|
||||
end = tzp.localize(datetime(2000, 1, 2), "Europe/Copenhagen")
|
||||
per = (start, end)
|
||||
assert vPeriod(per).to_ical() == b'20000101T000000/20000102T000000'
|
||||
assert vPeriod(per).params['TZID'] == 'Europe/Copenhagen'
|
||||
assert vPeriod(per).to_ical() == b"20000101T000000/20000102T000000"
|
||||
assert vPeriod(per).params["TZID"] == "Europe/Copenhagen"
|
||||
|
||||
|
||||
def test_timezoned_with_timedelta(tzp):
|
||||
p = vPeriod((tzp.localize(datetime(2000, 1, 1), 'Europe/Copenhagen'), timedelta(days=31)))
|
||||
assert p.to_ical() == b'20000101T000000/P31D'
|
||||
p = vPeriod(
|
||||
(tzp.localize(datetime(2000, 1, 1), "Europe/Copenhagen"), timedelta(days=31))
|
||||
)
|
||||
assert p.to_ical() == b"20000101T000000/P31D"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"params",
|
||||
[
|
||||
('20000101T000000', datetime(2000, 1, 2)),
|
||||
(datetime(2000, 1, 1), '20000102T000000'),
|
||||
("20000101T000000", datetime(2000, 1, 2)),
|
||||
(datetime(2000, 1, 1), "20000102T000000"),
|
||||
(datetime(2000, 1, 2), datetime(2000, 1, 1)),
|
||||
(datetime(2000, 1, 2), timedelta(-1)),
|
||||
]
|
||||
],
|
||||
)
|
||||
def test_invalid_parameters(params):
|
||||
"""The parameters are of wrong type or of wrong order."""
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
"""Test the mappings from windows to olson tzids"""
|
||||
from icalendar.timezone.windows_to_olson import WINDOWS_TO_OLSON
|
||||
import pytest
|
||||
from icalendar import vDatetime
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from icalendar import vDatetime
|
||||
from icalendar.timezone.windows_to_olson import WINDOWS_TO_OLSON
|
||||
|
||||
|
||||
def test_windows_timezone(tzp):
|
||||
"""Test that the timezone is mapped correctly to olson."""
|
||||
dt = vDatetime.from_ical('20170507T181920', 'Eastern Standard Time')
|
||||
expected = tzp.localize(datetime(2017, 5, 7, 18, 19, 20), 'America/New_York')
|
||||
dt = vDatetime.from_ical("20170507T181920", "Eastern Standard Time")
|
||||
expected = tzp.localize(datetime(2017, 5, 7, 18, 19, 20), "America/New_York")
|
||||
assert dt.tzinfo == dt.tzinfo
|
||||
assert dt == expected
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
def test_bom_calendar(calendars):
|
||||
assert calendars.bom_calendar.walk('VCALENDAR'), "Unable to parse a calendar starting with an Unicode BOM"
|
||||
assert calendars.bom_calendar.walk(
|
||||
"VCALENDAR"
|
||||
), "Unable to parse a calendar starting with an Unicode BOM"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import unittest
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from icalendar import Calendar, cli
|
||||
|
||||
try:
|
||||
import zoneinfo
|
||||
except ModuleNotFoundError:
|
||||
from backports import zoneinfo
|
||||
|
||||
INPUT = '''
|
||||
INPUT = """
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
|
|
@ -39,16 +40,23 @@ DTSTART:20220511
|
|||
DURATION:P5D
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
def local_datetime(dt):
|
||||
return datetime.strptime(dt, "%Y%m%dT%H%M%S").replace(tzinfo=zoneinfo.ZoneInfo("Europe/Warsaw")).astimezone().strftime('%c')
|
||||
return (
|
||||
datetime.strptime(dt, "%Y%m%dT%H%M%S")
|
||||
.replace(tzinfo=zoneinfo.ZoneInfo("Europe/Warsaw"))
|
||||
.astimezone()
|
||||
.strftime("%c")
|
||||
)
|
||||
|
||||
|
||||
# datetimes are displayed in the local timezone, so we cannot just hardcode them
|
||||
firststart = local_datetime('20220820T103400')
|
||||
firstend = local_datetime('20220820T113400')
|
||||
secondstart = local_datetime('20220820T200000')
|
||||
secondend = local_datetime('20220820T203000')
|
||||
firststart = local_datetime("20220820T103400")
|
||||
firstend = local_datetime("20220820T113400")
|
||||
secondstart = local_datetime("20220820T200000")
|
||||
secondend = local_datetime("20220820T203000")
|
||||
|
||||
PROPER_OUTPUT = f""" Organizer: organizer <organizer@test.test>
|
||||
Attendees:
|
||||
|
|
@ -91,15 +99,16 @@ PROPER_OUTPUT = f""" Organizer: organizer <organizer@test.test>
|
|||
|
||||
"""
|
||||
|
||||
|
||||
class CLIToolTest(unittest.TestCase):
|
||||
def test_output_is_proper(self):
|
||||
self.maxDiff = None
|
||||
calendar = Calendar.from_ical(INPUT)
|
||||
output = ''
|
||||
for event in calendar.walk('vevent'):
|
||||
output += cli.view(event) + '\n\n'
|
||||
output = ""
|
||||
for event in calendar.walk("vevent"):
|
||||
output += cli.view(event) + "\n\n"
|
||||
self.assertEqual(PROPER_OUTPUT, output)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -2,36 +2,47 @@ import pytest
|
|||
|
||||
|
||||
def test_ignore_exceptions_on_broken_events_issue_104(events):
|
||||
''' Issue #104 - line parsing error in a VEVENT
|
||||
"""Issue #104 - line parsing error in a VEVENT
|
||||
(which has ignore_exceptions). Should mark the event broken
|
||||
but not raise an exception.
|
||||
|
||||
https://github.com/collective/icalendar/issues/104
|
||||
'''
|
||||
assert events.issue_104_mark_events_broken.errors == [(None, "Content line could not be parsed into parts: 'X': Invalid content line")]
|
||||
"""
|
||||
assert events.issue_104_mark_events_broken.errors == [
|
||||
(None, "Content line could not be parsed into parts: 'X': Invalid content line")
|
||||
]
|
||||
|
||||
|
||||
def test_dont_ignore_exceptions_on_broken_calendars_issue_104(calendars):
|
||||
'''Issue #104 - line parsing error in a VCALENDAR
|
||||
"""Issue #104 - line parsing error in a VCALENDAR
|
||||
(which doesn't have ignore_exceptions). Should raise an exception.
|
||||
'''
|
||||
"""
|
||||
with pytest.raises(ValueError):
|
||||
calendars.issue_104_broken_calendar
|
||||
|
||||
def test_rdate_dosent_become_none_on_invalid_input_issue_464(events):
|
||||
'''Issue #464 - [BUG] RDATE can become None if value is invalid
|
||||
https://github.com/collective/icalendar/issues/464
|
||||
'''
|
||||
assert ('RDATE', 'Expected period format, got: 199709T180000Z/PT5H30M') in events.issue_464_invalid_rdate.errors
|
||||
assert b'RDATE:None' not in events.issue_464_invalid_rdate.to_ical()
|
||||
|
||||
@pytest.mark.parametrize('calendar_name', [
|
||||
'big_bad_calendar',
|
||||
'small_bad_calendar',
|
||||
'multiple_calendar_components',
|
||||
'pr_480_summary_with_colon',
|
||||
])
|
||||
def test_rdate_dosent_become_none_on_invalid_input_issue_464(events):
|
||||
"""Issue #464 - [BUG] RDATE can become None if value is invalid
|
||||
https://github.com/collective/icalendar/issues/464
|
||||
"""
|
||||
assert (
|
||||
"RDATE",
|
||||
"Expected period format, got: 199709T180000Z/PT5H30M",
|
||||
) in events.issue_464_invalid_rdate.errors
|
||||
assert b"RDATE:None" not in events.issue_464_invalid_rdate.to_ical()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"calendar_name",
|
||||
[
|
||||
"big_bad_calendar",
|
||||
"small_bad_calendar",
|
||||
"multiple_calendar_components",
|
||||
"pr_480_summary_with_colon",
|
||||
],
|
||||
)
|
||||
def test_error_message_doesnt_get_too_big(calendars, calendar_name):
|
||||
with pytest.raises(ValueError) as exception:
|
||||
calendars[calendar_name]
|
||||
# Ignore part before first : for the test.
|
||||
assert len(str(exception).split(': ', 1)[1]) <= 100
|
||||
assert len(str(exception).split(": ", 1)[1]) <= 100
|
||||
|
|
|
|||
|
|
@ -1,71 +1,98 @@
|
|||
import pytest
|
||||
import datetime
|
||||
|
||||
@pytest.mark.parametrize('field, expected_value', [
|
||||
('PRODID', '-//Plönë.org//NONSGML plone.app.event//EN'),
|
||||
('X-WR-CALDESC', 'test non ascii: äöü ÄÖÜ €'),
|
||||
])
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("field", "expected_value"),
|
||||
[
|
||||
("PRODID", "-//Plönë.org//NONSGML plone.app.event//EN"),
|
||||
("X-WR-CALDESC", "test non ascii: äöü ÄÖÜ €"),
|
||||
],
|
||||
)
|
||||
def test_calendar_from_ical_respects_unicode(field, expected_value, calendars):
|
||||
cal = calendars.calendar_with_unicode
|
||||
assert cal[field].to_ical().decode('utf-8') == expected_value
|
||||
assert cal[field].to_ical().decode("utf-8") == expected_value
|
||||
|
||||
@pytest.mark.parametrize('test_input, field, expected_value', [
|
||||
('event_with_unicode_fields', 'SUMMARY', 'Non-ASCII Test: ÄÖÜ äöü €'),
|
||||
('event_with_unicode_fields', 'DESCRIPTION', 'icalendar should be able to handle non-ascii: €äüöÄÜÖ.'),
|
||||
('event_with_unicode_fields', 'LOCATION', 'Tribstrül'),
|
||||
# Non-unicode characters in summary
|
||||
# https://github.com/collective/icalendar/issues/64
|
||||
('issue_64_event_with_non_ascii_summary', 'SUMMARY', 'åäö'),
|
||||
# Unicode characters in summary
|
||||
('issue_64_event_with_ascii_summary', 'SUMMARY', 'abcdef'),
|
||||
])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("test_input", "field", "expected_value"),
|
||||
[
|
||||
("event_with_unicode_fields", "SUMMARY", "Non-ASCII Test: ÄÖÜ äöü €"),
|
||||
(
|
||||
"event_with_unicode_fields",
|
||||
"DESCRIPTION",
|
||||
"icalendar should be able to handle non-ascii: €äüöÄÜÖ.",
|
||||
),
|
||||
("event_with_unicode_fields", "LOCATION", "Tribstrül"),
|
||||
# Non-unicode characters in summary
|
||||
# https://github.com/collective/icalendar/issues/64
|
||||
("issue_64_event_with_non_ascii_summary", "SUMMARY", "åäö"),
|
||||
# Unicode characters in summary
|
||||
("issue_64_event_with_ascii_summary", "SUMMARY", "abcdef"),
|
||||
],
|
||||
)
|
||||
def test_event_from_ical_respects_unicode(test_input, field, expected_value, events):
|
||||
event = events[test_input]
|
||||
assert event[field].to_ical().decode('utf-8') == expected_value
|
||||
assert event[field].to_ical().decode("utf-8") == expected_value
|
||||
|
||||
@pytest.mark.parametrize('test_input, expected_output', [
|
||||
# chokes on umlauts in ORGANIZER
|
||||
# https://github.com/collective/icalendar/issues/101
|
||||
('issue_101_icalendar_chokes_on_umlauts_in_organizer', 'acme, ädmin'),
|
||||
('event_with_unicode_organizer', 'Джон Доу'),
|
||||
])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("test_input", "expected_output"),
|
||||
[
|
||||
# chokes on umlauts in ORGANIZER
|
||||
# https://github.com/collective/icalendar/issues/101
|
||||
("issue_101_icalendar_chokes_on_umlauts_in_organizer", "acme, ädmin"),
|
||||
("event_with_unicode_organizer", "Джон Доу"),
|
||||
],
|
||||
)
|
||||
def test_events_parameter_unicoded(events, test_input, expected_output):
|
||||
assert events[test_input]['ORGANIZER'].params['CN'] == expected_output
|
||||
assert events[test_input]["ORGANIZER"].params["CN"] == expected_output
|
||||
|
||||
|
||||
def test_parses_event_with_non_ascii_tzid_issue_237(calendars, in_timezone):
|
||||
"""Issue #237 - Fail to parse timezone with non-ascii TZID
|
||||
see https://github.com/collective/icalendar/issues/237
|
||||
"""
|
||||
start = calendars.issue_237_fail_to_parse_timezone_with_non_ascii_tzid.walk('VEVENT')[0].decoded('DTSTART')
|
||||
expected = in_timezone(datetime.datetime(2017, 5, 11, 13, 30), 'America/Sao_Paulo')
|
||||
start = calendars.issue_237_fail_to_parse_timezone_with_non_ascii_tzid.walk(
|
||||
"VEVENT"
|
||||
)[0].decoded("DTSTART")
|
||||
expected = in_timezone(datetime.datetime(2017, 5, 11, 13, 30), "America/Sao_Paulo")
|
||||
assert not calendars.issue_237_fail_to_parse_timezone_with_non_ascii_tzid.errors
|
||||
assert start == expected
|
||||
|
||||
|
||||
def test_parses_timezone_with_non_ascii_tzid_issue_237(timezones):
|
||||
"""Issue #237 - Fail to parse timezone with non-ascii TZID
|
||||
see https://github.com/collective/icalendar/issues/237
|
||||
"""
|
||||
assert timezones.issue_237_brazilia_standard['tzid'] == '(UTC-03:00) Brasília'
|
||||
assert timezones.issue_237_brazilia_standard["tzid"] == "(UTC-03:00) Brasília"
|
||||
|
||||
@pytest.mark.parametrize('timezone_name', ['standard', 'daylight'])
|
||||
|
||||
@pytest.mark.parametrize("timezone_name", ["standard", "daylight"])
|
||||
def test_parses_timezone_with_non_ascii_tzname_issue_273(timezones, timezone_name):
|
||||
"""Issue #237 - Fail to parse timezone with non-ascii TZID
|
||||
see https://github.com/collective/icalendar/issues/237
|
||||
"""
|
||||
assert timezones.issue_237_brazilia_standard.walk(timezone_name)[0]['TZNAME'] == f'Brasília {timezone_name}'
|
||||
assert (
|
||||
timezones.issue_237_brazilia_standard.walk(timezone_name)[0]["TZNAME"]
|
||||
== f"Brasília {timezone_name}"
|
||||
)
|
||||
|
||||
|
||||
def test_broken_property(calendars):
|
||||
"""
|
||||
Test if error messages are encoded properly.
|
||||
"""
|
||||
for event in calendars.broken_ical.walk('vevent'):
|
||||
assert len(event.errors) == 1, 'Not the right amount of errors.'
|
||||
for event in calendars.broken_ical.walk("vevent"):
|
||||
assert len(event.errors) == 1, "Not the right amount of errors."
|
||||
error = event.errors[0][1]
|
||||
assert error.startswith('Content line could not be parsed into parts')
|
||||
assert error.startswith("Content line could not be parsed into parts")
|
||||
|
||||
|
||||
def test_apple_xlocation(calendars):
|
||||
"""
|
||||
Test if we support base64 encoded binary data in parameter values.
|
||||
"""
|
||||
for event in calendars.x_location.walk('vevent'):
|
||||
assert len(event.errors) == 0, 'Got too many errors'
|
||||
for event in calendars.x_location.walk("vevent"):
|
||||
assert len(event.errors) == 0, "Got too many errors"
|
||||
|
|
|
|||
|
|
@ -1,22 +1,42 @@
|
|||
"""Test the equality and inequality of components."""
|
||||
|
||||
import contextlib
|
||||
import copy
|
||||
|
||||
try:
|
||||
import pytz
|
||||
from pytz import UnknownTimeZoneError
|
||||
except ImportError:
|
||||
pytz = None
|
||||
from icalendar.prop import *
|
||||
from datetime import datetime, date, time, timedelta
|
||||
class UnknownTimeZoneError(Exception):
|
||||
pass
|
||||
from datetime import date, datetime, time, timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
from icalendar.prop import (
|
||||
vBinary,
|
||||
vBoolean,
|
||||
vCategory,
|
||||
vDate,
|
||||
vDatetime,
|
||||
vDDDLists,
|
||||
vDDDTypes,
|
||||
vDuration,
|
||||
vGeo,
|
||||
vPeriod,
|
||||
vText,
|
||||
vTime,
|
||||
)
|
||||
|
||||
|
||||
def assert_equal(actual_value, expected_value):
|
||||
"""Make sure both values are equal"""
|
||||
assert actual_value == expected_value
|
||||
assert not actual_value != expected_value
|
||||
assert actual_value == expected_value
|
||||
|
||||
|
||||
def assert_not_equal(actual_value, expected_value):
|
||||
"""Make sure both values are not equal"""
|
||||
assert not actual_value == expected_value
|
||||
assert actual_value != expected_value
|
||||
assert actual_value != expected_value
|
||||
|
||||
|
||||
|
|
@ -42,8 +62,10 @@ def test_parsed_calendars_are_equal_if_from_same_source(ics_file, tzp):
|
|||
|
||||
def test_copies_are_equal(ics_file, tzp):
|
||||
"""Ensure that copies are equal."""
|
||||
copy1 = ics_file.copy(); copy1.subcomponents = ics_file.subcomponents
|
||||
copy2 = ics_file.copy(); copy2.subcomponents = ics_file.subcomponents[:]
|
||||
copy1 = ics_file.copy()
|
||||
copy1.subcomponents = ics_file.subcomponents
|
||||
copy2 = ics_file.copy()
|
||||
copy2.subcomponents = ics_file.subcomponents[:]
|
||||
assert_equal(copy1, copy2)
|
||||
assert_equal(copy1, ics_file)
|
||||
assert_equal(copy2, ics_file)
|
||||
|
|
@ -56,20 +78,15 @@ def test_copy_does_not_copy_subcomponents(calendars, tzp):
|
|||
|
||||
|
||||
def test_deep_copies_are_equal(ics_file, tzp):
|
||||
"""Ensure that deep copies are equal."""
|
||||
try:
|
||||
assert_equal(copy.deepcopy(ics_file), copy.deepcopy(ics_file))
|
||||
except pytz.UnknownTimeZoneError:
|
||||
# Ignore errors when a custom time zone is used.
|
||||
# This is still covered by the parsing test.
|
||||
pass
|
||||
try:
|
||||
assert_equal(copy.deepcopy(ics_file), ics_file)
|
||||
except pytz.UnknownTimeZoneError:
|
||||
# Ignore errors when a custom time zone is used.
|
||||
# This is still covered by the parsing test.
|
||||
pass
|
||||
"""Ensure that deep copies are equal.
|
||||
|
||||
Ignore errors when a custom time zone is used.
|
||||
This is still covered by the parsing test.
|
||||
"""
|
||||
with contextlib.suppress(UnknownTimeZoneError):
|
||||
assert_equal(copy.deepcopy(ics_file), copy.deepcopy(ics_file))
|
||||
with contextlib.suppress(UnknownTimeZoneError):
|
||||
assert_equal(copy.deepcopy(ics_file), ics_file)
|
||||
|
||||
|
||||
def test_vGeo():
|
||||
|
|
@ -80,20 +97,20 @@ def test_vGeo():
|
|||
|
||||
|
||||
def test_vBinary():
|
||||
assert_equal(vBinary('asd'), vBinary('asd'))
|
||||
assert_not_equal(vBinary('asdf'), vBinary('asd'))
|
||||
assert_equal(vBinary("asd"), vBinary("asd"))
|
||||
assert_not_equal(vBinary("asdf"), vBinary("asd"))
|
||||
|
||||
|
||||
def test_vBoolean():
|
||||
assert_equal(vBoolean.from_ical('TRUE'), vBoolean.from_ical('TRUE'))
|
||||
assert_equal(vBoolean.from_ical('FALSE'), vBoolean.from_ical('FALSE'))
|
||||
assert_not_equal(vBoolean.from_ical('TRUE'), vBoolean.from_ical('FALSE'))
|
||||
assert_equal(vBoolean.from_ical("TRUE"), vBoolean.from_ical("TRUE"))
|
||||
assert_equal(vBoolean.from_ical("FALSE"), vBoolean.from_ical("FALSE"))
|
||||
assert_not_equal(vBoolean.from_ical("TRUE"), vBoolean.from_ical("FALSE"))
|
||||
|
||||
|
||||
def test_vCategory():
|
||||
assert_equal(vCategory("HELLO"), vCategory("HELLO"))
|
||||
assert_equal(vCategory(["a","b"]), vCategory(["a","b"]))
|
||||
assert_not_equal(vCategory(["a","b"]), vCategory(["a","b", "c"]))
|
||||
assert_equal(vCategory(["a", "b"]), vCategory(["a", "b"]))
|
||||
assert_not_equal(vCategory(["a", "b"]), vCategory(["a", "b", "c"]))
|
||||
|
||||
|
||||
def test_vText():
|
||||
|
|
@ -102,21 +119,33 @@ def test_vText():
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"vType,v1,v2",
|
||||
("vType", "v1", "v2"),
|
||||
[
|
||||
(vDatetime, datetime(2023, 11, 1, 10, 11), datetime(2023, 11, 1, 10, 10)),
|
||||
(vDate, date(2023, 11, 1), date(2023, 10, 31)),
|
||||
(vDuration, timedelta(3, 11, 1), timedelta(23, 10, 31)),
|
||||
(vPeriod, (datetime(2023, 11, 1, 10, 11), timedelta(3, 11, 1)), (datetime(2023, 11, 1, 10, 11), timedelta(23, 10, 31))),
|
||||
(vPeriod, (datetime(2023, 11, 1, 10, 1), timedelta(3, 11, 1)), (datetime(2023, 11, 1, 10, 11), timedelta(3, 11, 1))),
|
||||
(vPeriod, (datetime(2023, 11, 1, 10, 1), datetime(2023, 11, 1, 10, 3)), (datetime(2023, 11, 1, 10, 1), datetime(2023, 11, 1, 10, 2))),
|
||||
(
|
||||
vPeriod,
|
||||
(datetime(2023, 11, 1, 10, 11), timedelta(3, 11, 1)),
|
||||
(datetime(2023, 11, 1, 10, 11), timedelta(23, 10, 31)),
|
||||
),
|
||||
(
|
||||
vPeriod,
|
||||
(datetime(2023, 11, 1, 10, 1), timedelta(3, 11, 1)),
|
||||
(datetime(2023, 11, 1, 10, 11), timedelta(3, 11, 1)),
|
||||
),
|
||||
(
|
||||
vPeriod,
|
||||
(datetime(2023, 11, 1, 10, 1), datetime(2023, 11, 1, 10, 3)),
|
||||
(datetime(2023, 11, 1, 10, 1), datetime(2023, 11, 1, 10, 2)),
|
||||
),
|
||||
(vTime, time(10, 10, 10), time(10, 10, 11)),
|
||||
]
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("eq", ["==", "!="])
|
||||
@pytest.mark.parametrize("cls1", [0, 1])
|
||||
@pytest.mark.parametrize("cls2", [0, 1])
|
||||
@pytest.mark.parametrize("hash", [lambda x:x, hash])
|
||||
@pytest.mark.parametrize("hash", [lambda x: x, hash])
|
||||
def test_vDDDTypes_and_others(vType, v1, v2, cls1, cls2, eq, hash):
|
||||
"""Check equality and inequality."""
|
||||
t1 = (vType, vDDDTypes)[cls1]
|
||||
|
|
@ -124,7 +153,7 @@ def test_vDDDTypes_and_others(vType, v1, v2, cls1, cls2, eq, hash):
|
|||
if eq == "==":
|
||||
assert hash(v1) == hash(v1)
|
||||
assert hash(t1(v1)) == hash(t2(v1))
|
||||
assert not hash(t1(v1)) != hash(t2(v1))
|
||||
assert hash(t1(v1)) == hash(t2(v1))
|
||||
else:
|
||||
assert hash(v1) != hash(v2)
|
||||
assert hash(t1(v1)) != hash(t2(v2))
|
||||
|
|
@ -134,11 +163,13 @@ def test_repr_vDDDTypes():
|
|||
assert "vDDDTypes" in repr(vDDDTypes(timedelta(3, 11, 1)))
|
||||
|
||||
|
||||
vDDDLists_examples = [
|
||||
vDDDLists_examples = [ # noqa: N816
|
||||
vDDDLists([]),
|
||||
vDDDLists([datetime(2023, 11, 1, 10, 1)]),
|
||||
vDDDLists([datetime(2023, 11, 1, 10, 1), date(2023, 11, 1)]),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("l1", vDDDLists_examples)
|
||||
@pytest.mark.parametrize("l2", vDDDLists_examples)
|
||||
def test_vDDDLists(l1, l2):
|
||||
|
|
|
|||
|
|
@ -1,51 +1,54 @@
|
|||
'''tests ensuring that *the* way of doing things works'''
|
||||
"""tests ensuring that *the* way of doing things works"""
|
||||
|
||||
import datetime
|
||||
from icalendar import Calendar, Event, Timezone
|
||||
|
||||
import pytest
|
||||
|
||||
from icalendar import Calendar, Event, Timezone
|
||||
|
||||
|
||||
def test_creating_calendar_with_unicode_fields(calendars, utc):
|
||||
''' create a calendar with events that contain unicode characters in their fields '''
|
||||
"""create a calendar with events that contain unicode characters in their fields"""
|
||||
cal = Calendar()
|
||||
cal.add('PRODID', '-//Plönë.org//NONSGML plone.app.event//EN')
|
||||
cal.add('VERSION', '2.0')
|
||||
cal.add('X-WR-CALNAME', 'äöü ÄÖÜ €')
|
||||
cal.add('X-WR-CALDESC', 'test non ascii: äöü ÄÖÜ €')
|
||||
cal.add('X-WR-RELCALID', '12345')
|
||||
cal.add("PRODID", "-//Plönë.org//NONSGML plone.app.event//EN")
|
||||
cal.add("VERSION", "2.0")
|
||||
cal.add("X-WR-CALNAME", "äöü ÄÖÜ €")
|
||||
cal.add("X-WR-CALDESC", "test non ascii: äöü ÄÖÜ €")
|
||||
cal.add("X-WR-RELCALID", "12345")
|
||||
|
||||
event = Event()
|
||||
event.add('DTSTART', datetime.datetime(2010, 10, 10, 10, 0, 0, tzinfo=utc))
|
||||
event.add('DTEND', datetime.datetime(2010, 10, 10, 12, 0, 0, tzinfo=utc))
|
||||
event.add('CREATED', datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=utc))
|
||||
event.add('UID', '123456')
|
||||
event.add('SUMMARY', 'Non-ASCII Test: ÄÖÜ äöü €')
|
||||
event.add('DESCRIPTION', 'icalendar should be able to de/serialize non-ascii.')
|
||||
event.add('LOCATION', 'Tribstrül')
|
||||
event.add("DTSTART", datetime.datetime(2010, 10, 10, 10, 0, 0, tzinfo=utc))
|
||||
event.add("DTEND", datetime.datetime(2010, 10, 10, 12, 0, 0, tzinfo=utc))
|
||||
event.add("CREATED", datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=utc))
|
||||
event.add("UID", "123456")
|
||||
event.add("SUMMARY", "Non-ASCII Test: ÄÖÜ äöü €")
|
||||
event.add("DESCRIPTION", "icalendar should be able to de/serialize non-ascii.")
|
||||
event.add("LOCATION", "Tribstrül")
|
||||
cal.add_component(event)
|
||||
|
||||
# test_create_event_simple
|
||||
event1 = Event()
|
||||
event1.add('DTSTART', datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=utc))
|
||||
event1.add('SUMMARY', 'åäö')
|
||||
event1.add("DTSTART", datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=utc))
|
||||
event1.add("SUMMARY", "åäö")
|
||||
cal.add_component(event1)
|
||||
|
||||
# test_unicode_parameter_name
|
||||
# test for issue #80 https://github.com/collective/icalendar/issues/80
|
||||
event2 = Event()
|
||||
event2.add('DESCRIPTION', 'äöüßÄÖÜ')
|
||||
event2.add("DESCRIPTION", "äöüßÄÖÜ")
|
||||
cal.add_component(event2)
|
||||
|
||||
assert cal.to_ical() == calendars.created_calendar_with_unicode_fields.raw_ics
|
||||
|
||||
|
||||
@pytest.mark.parametrize("component,example",
|
||||
@pytest.mark.parametrize(
|
||||
("component", "example"),
|
||||
[
|
||||
(Calendar, "example"),
|
||||
(Calendar, "example.ics"),
|
||||
(Event, "event_with_rsvp"),
|
||||
(Timezone, "pacific_fiji"),
|
||||
]
|
||||
],
|
||||
)
|
||||
def test_component_has_examples(tzp, calendars, timezones, events, component, example):
|
||||
"""Check that the examples function works."""
|
||||
|
|
|
|||
|
|
@ -1,59 +1,73 @@
|
|||
from ..parser import Contentlines, Contentline, Parameters, foldline
|
||||
from ..parser import q_join, q_split, dquote
|
||||
from ..prop import vText
|
||||
|
||||
import unittest
|
||||
|
||||
from icalendar.parser import (
|
||||
Contentline,
|
||||
Contentlines,
|
||||
Parameters,
|
||||
dquote,
|
||||
foldline,
|
||||
q_join,
|
||||
q_split,
|
||||
)
|
||||
from icalendar.prop import vText
|
||||
|
||||
class IcalendarTestCase (unittest.TestCase):
|
||||
|
||||
class IcalendarTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if not hasattr(self, 'assertRaisesRegex'):
|
||||
if not hasattr(self, "assertRaisesRegex"):
|
||||
self.assertRaisesRegex = self.assertRaisesRegexp
|
||||
|
||||
def test_long_lines(self):
|
||||
c = Contentlines([Contentline('BEGIN:VEVENT')])
|
||||
c.append(Contentline(''.join('123456789 ' * 10)))
|
||||
c = Contentlines([Contentline("BEGIN:VEVENT")])
|
||||
c.append(Contentline("".join("123456789 " * 10)))
|
||||
self.assertEqual(
|
||||
c.to_ical(),
|
||||
b'BEGIN:VEVENT\r\n123456789 123456789 123456789 123456789 '
|
||||
b'123456789 123456789 123456789 1234\r\n 56789 123456789 '
|
||||
b'123456789 \r\n'
|
||||
b"BEGIN:VEVENT\r\n123456789 123456789 123456789 123456789 "
|
||||
b"123456789 123456789 123456789 1234\r\n 56789 123456789 "
|
||||
b"123456789 \r\n",
|
||||
)
|
||||
|
||||
# from doctests
|
||||
# Notice that there is an extra empty string in the end of the content
|
||||
# lines. That is so they can be easily joined with:
|
||||
# '\r\n'.join(contentlines))
|
||||
self.assertEqual(Contentlines.from_ical('A short line\r\n'),
|
||||
['A short line', ''])
|
||||
self.assertEqual(Contentlines.from_ical('A faked\r\n long line\r\n'),
|
||||
['A faked long line', ''])
|
||||
self.assertEqual(
|
||||
Contentlines.from_ical('A faked\r\n long line\r\nAnd another '
|
||||
'lin\r\n\te that is folded\r\n'),
|
||||
['A faked long line', 'And another line that is folded', '']
|
||||
Contentlines.from_ical("A short line\r\n"), ["A short line", ""]
|
||||
)
|
||||
self.assertEqual(
|
||||
Contentlines.from_ical("A faked\r\n long line\r\n"),
|
||||
["A faked long line", ""],
|
||||
)
|
||||
self.assertEqual(
|
||||
Contentlines.from_ical(
|
||||
"A faked\r\n long line\r\nAnd another " "lin\r\n\te that is folded\r\n"
|
||||
),
|
||||
["A faked long line", "And another line that is folded", ""],
|
||||
)
|
||||
|
||||
def test_contentline_class(self):
|
||||
self.assertEqual(
|
||||
Contentline('Si meliora dies, ut vina, poemata reddit').to_ical(),
|
||||
b'Si meliora dies, ut vina, poemata reddit'
|
||||
Contentline("Si meliora dies, ut vina, poemata reddit").to_ical(),
|
||||
b"Si meliora dies, ut vina, poemata reddit",
|
||||
)
|
||||
|
||||
# A long line gets folded
|
||||
c = Contentline(''.join(['123456789 '] * 10)).to_ical()
|
||||
c = Contentline("".join(["123456789 "] * 10)).to_ical()
|
||||
self.assertEqual(
|
||||
c,
|
||||
(b'123456789 123456789 123456789 123456789 123456789 123456789 '
|
||||
b'123456789 1234\r\n 56789 123456789 123456789 ')
|
||||
(
|
||||
b"123456789 123456789 123456789 123456789 123456789 123456789 "
|
||||
b"123456789 1234\r\n 56789 123456789 123456789 "
|
||||
),
|
||||
)
|
||||
|
||||
# A folded line gets unfolded
|
||||
self.assertEqual(
|
||||
Contentline.from_ical(c),
|
||||
('123456789 123456789 123456789 123456789 123456789 123456789 '
|
||||
'123456789 123456789 123456789 123456789 ')
|
||||
(
|
||||
"123456789 123456789 123456789 123456789 123456789 123456789 "
|
||||
"123456789 123456789 123456789 123456789 "
|
||||
),
|
||||
)
|
||||
|
||||
# https://tools.ietf.org/html/rfc5545#section-3.3.11
|
||||
|
|
@ -63,219 +77,228 @@ class IcalendarTestCase (unittest.TestCase):
|
|||
# N or a LATIN CAPITAL LETTER N, that is "\n" or "\N".
|
||||
|
||||
# Newlines are not allowed in content lines
|
||||
self.assertRaises(AssertionError, Contentline, b'1234\r\n\r\n1234')
|
||||
self.assertRaises(AssertionError, Contentline, b"1234\r\n\r\n1234")
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('1234\\n\\n1234').to_ical(),
|
||||
b'1234\\n\\n1234'
|
||||
)
|
||||
self.assertEqual(Contentline("1234\\n\\n1234").to_ical(), b"1234\\n\\n1234")
|
||||
|
||||
# We do not fold within a UTF-8 character
|
||||
c = Contentline(b'This line has a UTF-8 character where it should be '
|
||||
b'folded. Make sure it g\xc3\xabts folded before that '
|
||||
b'character.')
|
||||
c = Contentline(
|
||||
b"This line has a UTF-8 character where it should be "
|
||||
b"folded. Make sure it g\xc3\xabts folded before that "
|
||||
b"character."
|
||||
)
|
||||
|
||||
self.assertIn(b'\xc3\xab', c.to_ical())
|
||||
self.assertIn(b"\xc3\xab", c.to_ical())
|
||||
|
||||
# Another test of the above
|
||||
c = Contentline(b'x' * 73 + b'\xc3\xab' + b'\\n ' + b'y' * 10)
|
||||
c = Contentline(b"x" * 73 + b"\xc3\xab" + b"\\n " + b"y" * 10)
|
||||
|
||||
self.assertEqual(c.to_ical().count(b'\xc3'), 1)
|
||||
self.assertEqual(c.to_ical().count(b"\xc3"), 1)
|
||||
|
||||
# Don't fail if we fold a line that is exactly X times 74 characters
|
||||
# long
|
||||
c = Contentline(''.join(['x'] * 148)).to_ical()
|
||||
c = Contentline("".join(["x"] * 148)).to_ical()
|
||||
|
||||
# It can parse itself into parts,
|
||||
# which is a tuple of (name, params, vals)
|
||||
self.assertEqual(
|
||||
Contentline('dtstart:20050101T120000').parts(),
|
||||
('dtstart', Parameters({}), '20050101T120000')
|
||||
Contentline("dtstart:20050101T120000").parts(),
|
||||
("dtstart", Parameters({}), "20050101T120000"),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('dtstart;value=datetime:20050101T120000').parts(),
|
||||
('dtstart', Parameters({'VALUE': 'datetime'}), '20050101T120000')
|
||||
Contentline("dtstart;value=datetime:20050101T120000").parts(),
|
||||
("dtstart", Parameters({"VALUE": "datetime"}), "20050101T120000"),
|
||||
)
|
||||
|
||||
c = Contentline('ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:'
|
||||
'MAILTO:maxm@example.com')
|
||||
c = Contentline(
|
||||
"ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:" "MAILTO:maxm@example.com"
|
||||
)
|
||||
self.assertEqual(
|
||||
c.parts(),
|
||||
('ATTENDEE',
|
||||
Parameters({'ROLE': 'REQ-PARTICIPANT', 'CN': 'Max Rasmussen'}),
|
||||
'MAILTO:maxm@example.com')
|
||||
(
|
||||
"ATTENDEE",
|
||||
Parameters({"ROLE": "REQ-PARTICIPANT", "CN": "Max Rasmussen"}),
|
||||
"MAILTO:maxm@example.com",
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
c.to_ical().decode('utf-8'),
|
||||
'ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:'
|
||||
'MAILTO:maxm@example.com'
|
||||
c.to_ical().decode("utf-8"),
|
||||
"ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:" "MAILTO:maxm@example.com",
|
||||
)
|
||||
|
||||
# and back again
|
||||
# NOTE: we are quoting property values with spaces in it.
|
||||
parts = ('ATTENDEE',
|
||||
Parameters({'ROLE': 'REQ-PARTICIPANT',
|
||||
'CN': 'Max Rasmussen'}),
|
||||
'MAILTO:maxm@example.com')
|
||||
parts = (
|
||||
"ATTENDEE",
|
||||
Parameters({"ROLE": "REQ-PARTICIPANT", "CN": "Max Rasmussen"}),
|
||||
"MAILTO:maxm@example.com",
|
||||
)
|
||||
self.assertEqual(
|
||||
Contentline.from_parts(*parts),
|
||||
'ATTENDEE;CN="Max Rasmussen";ROLE=REQ-PARTICIPANT:'
|
||||
'MAILTO:maxm@example.com'
|
||||
"MAILTO:maxm@example.com",
|
||||
)
|
||||
|
||||
# and again
|
||||
parts = ('ATTENDEE', Parameters(), 'MAILTO:maxm@example.com')
|
||||
parts = ("ATTENDEE", Parameters(), "MAILTO:maxm@example.com")
|
||||
self.assertEqual(
|
||||
Contentline.from_parts(*parts),
|
||||
'ATTENDEE:MAILTO:maxm@example.com'
|
||||
Contentline.from_parts(*parts), "ATTENDEE:MAILTO:maxm@example.com"
|
||||
)
|
||||
|
||||
# A value can also be any of the types defined in PropertyValues
|
||||
parts = ('ATTENDEE', Parameters(), vText('MAILTO:test@example.com'))
|
||||
parts = ("ATTENDEE", Parameters(), vText("MAILTO:test@example.com"))
|
||||
self.assertEqual(
|
||||
Contentline.from_parts(*parts),
|
||||
'ATTENDEE:MAILTO:test@example.com'
|
||||
Contentline.from_parts(*parts), "ATTENDEE:MAILTO:test@example.com"
|
||||
)
|
||||
|
||||
# A value in UTF-8
|
||||
parts = ('SUMMARY', Parameters(), vText('INternational char æ ø å'))
|
||||
parts = ("SUMMARY", Parameters(), vText("INternational char æ ø å"))
|
||||
self.assertEqual(
|
||||
Contentline.from_parts(*parts),
|
||||
'SUMMARY:INternational char æ ø å'
|
||||
Contentline.from_parts(*parts), "SUMMARY:INternational char æ ø å"
|
||||
)
|
||||
|
||||
# A value can also be unicode
|
||||
parts = ('SUMMARY', Parameters(), vText('INternational char æ ø å'))
|
||||
parts = ("SUMMARY", Parameters(), vText("INternational char æ ø å"))
|
||||
self.assertEqual(
|
||||
Contentline.from_parts(*parts),
|
||||
'SUMMARY:INternational char æ ø å'
|
||||
Contentline.from_parts(*parts), "SUMMARY:INternational char æ ø å"
|
||||
)
|
||||
|
||||
# Traversing could look like this.
|
||||
name, params, vals = c.parts()
|
||||
self.assertEqual(name, 'ATTENDEE')
|
||||
self.assertEqual(vals, 'MAILTO:maxm@example.com')
|
||||
self.assertEqual(name, "ATTENDEE")
|
||||
self.assertEqual(vals, "MAILTO:maxm@example.com")
|
||||
self.assertEqual(
|
||||
sorted(params.items()),
|
||||
sorted([('ROLE', 'REQ-PARTICIPANT'), ('CN', 'Max Rasmussen')])
|
||||
sorted([("ROLE", "REQ-PARTICIPANT"), ("CN", "Max Rasmussen")]),
|
||||
)
|
||||
|
||||
# And the traditional failure
|
||||
with self.assertRaisesRegex(
|
||||
ValueError,
|
||||
'Content line could not be parsed into parts'
|
||||
ValueError, "Content line could not be parsed into parts"
|
||||
):
|
||||
Contentline('ATTENDEE;maxm@example.com').parts()
|
||||
Contentline("ATTENDEE;maxm@example.com").parts()
|
||||
|
||||
# Another failure:
|
||||
with self.assertRaisesRegex(
|
||||
ValueError,
|
||||
'Content line could not be parsed into parts'
|
||||
ValueError, "Content line could not be parsed into parts"
|
||||
):
|
||||
Contentline(':maxm@example.com').parts()
|
||||
Contentline(":maxm@example.com").parts()
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('key;param=:value').parts(),
|
||||
('key', Parameters({'PARAM': ''}), 'value')
|
||||
Contentline("key;param=:value").parts(),
|
||||
("key", Parameters({"PARAM": ""}), "value"),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('key;param="pvalue":value').parts(),
|
||||
('key', Parameters({'PARAM': 'pvalue'}), 'value')
|
||||
("key", Parameters({"PARAM": "pvalue"}), "value"),
|
||||
)
|
||||
|
||||
# Should bomb on missing param:
|
||||
with self.assertRaisesRegex(
|
||||
ValueError,
|
||||
'Content line could not be parsed into parts'
|
||||
ValueError, "Content line could not be parsed into parts"
|
||||
):
|
||||
Contentline.from_ical("k;:no param").parts()
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('key;param=pvalue:value', strict=False).parts(),
|
||||
('key', Parameters({'PARAM': 'pvalue'}), 'value')
|
||||
Contentline("key;param=pvalue:value", strict=False).parts(),
|
||||
("key", Parameters({"PARAM": "pvalue"}), "value"),
|
||||
)
|
||||
|
||||
# If strict is set to True, uppercase param values that are not
|
||||
# double-quoted, this is because the spec says non-quoted params are
|
||||
# case-insensitive.
|
||||
self.assertEqual(
|
||||
Contentline('key;param=pvalue:value', strict=True).parts(),
|
||||
('key', Parameters({'PARAM': 'PVALUE'}), 'value')
|
||||
Contentline("key;param=pvalue:value", strict=True).parts(),
|
||||
("key", Parameters({"PARAM": "PVALUE"}), "value"),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('key;param="pValue":value', strict=True).parts(),
|
||||
('key', Parameters({'PARAM': 'pValue'}), 'value')
|
||||
("key", Parameters({"PARAM": "pValue"}), "value"),
|
||||
)
|
||||
|
||||
contains_base64 = (
|
||||
b'X-APPLE-STRUCTURED-LOCATION;'
|
||||
b"X-APPLE-STRUCTURED-LOCATION;"
|
||||
b'VALUE=URI;X-ADDRESS="Kaiserliche Hofburg, 1010 Wien";'
|
||||
b'X-APPLE-MAPKIT-HANDLE=CAESxQEZgr3QZXJyZWljaA==;'
|
||||
b'X-APPLE-RADIUS=328.7978217977285;X-APPLE-REFERENCEFRAME=1;'
|
||||
b'X-TITLE=Heldenplatz:geo:48.206686,16.363235'
|
||||
b"X-APPLE-MAPKIT-HANDLE=CAESxQEZgr3QZXJyZWljaA==;"
|
||||
b"X-APPLE-RADIUS=328.7978217977285;X-APPLE-REFERENCEFRAME=1;"
|
||||
b"X-TITLE=Heldenplatz:geo:48.206686,16.363235"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
Contentline(contains_base64, strict=True).parts(),
|
||||
('X-APPLE-STRUCTURED-LOCATION',
|
||||
Parameters({
|
||||
'X-APPLE-RADIUS': '328.7978217977285',
|
||||
'X-ADDRESS': 'Kaiserliche Hofburg, 1010 Wien',
|
||||
'X-APPLE-REFERENCEFRAME': '1',
|
||||
'X-TITLE': 'HELDENPLATZ',
|
||||
'X-APPLE-MAPKIT-HANDLE':
|
||||
'CAESXQEZGR3QZXJYZWLJAA==',
|
||||
'VALUE': 'URI',
|
||||
}),
|
||||
'geo:48.206686,16.363235'
|
||||
)
|
||||
(
|
||||
"X-APPLE-STRUCTURED-LOCATION",
|
||||
Parameters(
|
||||
{
|
||||
"X-APPLE-RADIUS": "328.7978217977285",
|
||||
"X-ADDRESS": "Kaiserliche Hofburg, 1010 Wien",
|
||||
"X-APPLE-REFERENCEFRAME": "1",
|
||||
"X-TITLE": "HELDENPLATZ",
|
||||
"X-APPLE-MAPKIT-HANDLE": "CAESXQEZGR3QZXJYZWLJAA==",
|
||||
"VALUE": "URI",
|
||||
}
|
||||
),
|
||||
"geo:48.206686,16.363235",
|
||||
),
|
||||
)
|
||||
|
||||
def test_fold_line(self):
|
||||
self.assertEqual(foldline('foo'), 'foo')
|
||||
self.assertEqual(foldline("foo"), "foo")
|
||||
self.assertEqual(
|
||||
foldline("Lorem ipsum dolor sit amet, consectetur adipiscing "
|
||||
"elit. Vestibulum convallis imperdiet dui posuere."),
|
||||
('Lorem ipsum dolor sit amet, consectetur adipiscing elit. '
|
||||
'Vestibulum conval\r\n lis imperdiet dui posuere.')
|
||||
foldline(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing "
|
||||
"elit. Vestibulum convallis imperdiet dui posuere."
|
||||
),
|
||||
(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
|
||||
"Vestibulum conval\r\n lis imperdiet dui posuere."
|
||||
),
|
||||
)
|
||||
|
||||
# I don't really get this test
|
||||
# at least just but bytes in there
|
||||
# porting it to "run" under python 2 & 3 makes it not much better
|
||||
with self.assertRaises(AssertionError):
|
||||
foldline('привет'.encode(), limit=3)
|
||||
foldline("привет".encode(), limit=3)
|
||||
|
||||
self.assertEqual(foldline('foobar', limit=4), 'foo\r\n bar')
|
||||
self.assertEqual(foldline("foobar", limit=4), "foo\r\n bar")
|
||||
self.assertEqual(
|
||||
foldline('Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
'. Vestibulum convallis imperdiet dui posuere.'),
|
||||
('Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
|
||||
' Vestibulum conval\r\n lis imperdiet dui posuere.')
|
||||
foldline(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit"
|
||||
". Vestibulum convallis imperdiet dui posuere."
|
||||
),
|
||||
(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
" Vestibulum conval\r\n lis imperdiet dui posuere."
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
foldline('DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ'),
|
||||
'DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭ\r\n ЮЯ'
|
||||
foldline("DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ"),
|
||||
"DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭ\r\n ЮЯ",
|
||||
)
|
||||
|
||||
def test_value_double_quoting(self):
|
||||
self.assertEqual(dquote('Max'), 'Max')
|
||||
self.assertEqual(dquote('Rasmussen, Max'), '"Rasmussen, Max"')
|
||||
self.assertEqual(dquote('name:value'), '"name:value"')
|
||||
self.assertEqual(dquote("Max"), "Max")
|
||||
self.assertEqual(dquote("Rasmussen, Max"), '"Rasmussen, Max"')
|
||||
self.assertEqual(dquote("name:value"), '"name:value"')
|
||||
|
||||
def test_q_split(self):
|
||||
self.assertEqual(q_split('Max,Moller,"Rasmussen, Max"'),
|
||||
['Max', 'Moller', '"Rasmussen, Max"'])
|
||||
self.assertEqual(
|
||||
q_split('Max,Moller,"Rasmussen, Max"'),
|
||||
["Max", "Moller", '"Rasmussen, Max"'],
|
||||
)
|
||||
|
||||
def test_q_split_bin(self):
|
||||
for s in ('X-SOMETHING=ABCDE==', ',,,'):
|
||||
for s in ("X-SOMETHING=ABCDE==", ",,,"):
|
||||
for maxsplit in range(-1, 3):
|
||||
self.assertEqual(q_split(s, '=', maxsplit=maxsplit),
|
||||
s.split('=', maxsplit))
|
||||
self.assertEqual(
|
||||
q_split(s, "=", maxsplit=maxsplit), s.split("=", maxsplit)
|
||||
)
|
||||
|
||||
def test_q_join(self):
|
||||
self.assertEqual(q_join(['Max', 'Moller', 'Rasmussen, Max']),
|
||||
'Max,Moller,"Rasmussen, Max"')
|
||||
self.assertEqual(
|
||||
q_join(["Max", "Moller", "Rasmussen, Max"]), 'Max,Moller,"Rasmussen, Max"'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import icalendar
|
||||
|
||||
|
||||
|
|
@ -15,14 +14,14 @@ def test_issue_116():
|
|||
"VALUE": "URI",
|
||||
"X-ADDRESS": "367 George Street Sydney CBD NSW 2000",
|
||||
"X-APPLE-RADIUS": "72",
|
||||
"X-TITLE": "367 George Street"
|
||||
}
|
||||
"X-TITLE": "367 George Street",
|
||||
},
|
||||
)
|
||||
assert event.to_ical() == (
|
||||
b'BEGIN:VEVENT\r\nX-APPLE-STRUCTURED-LOCATION;VALUE=URI;'
|
||||
b"BEGIN:VEVENT\r\nX-APPLE-STRUCTURED-LOCATION;VALUE=URI;"
|
||||
b'X-ADDRESS="367 George Street Sydney \r\n CBD NSW 2000";'
|
||||
b'X-APPLE-RADIUS=72;X-TITLE="367 George Street":'
|
||||
b'geo:-33.868900\r\n \\,151.207000\r\nEND:VEVENT\r\n'
|
||||
b"geo:-33.868900\r\n \\,151.207000\r\nEND:VEVENT\r\n"
|
||||
)
|
||||
|
||||
# roundtrip
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
'''Issue #165 - Problem parsing a file with event recurring on weekdays
|
||||
"""Issue #165 - Problem parsing a file with event recurring on weekdays
|
||||
|
||||
https://github.com/collective/icalendar/issues/165
|
||||
"""
|
||||
|
||||
https://github.com/collective/icalendar/issues/165
|
||||
'''
|
||||
|
||||
def test_issue_165_missing_event(calendars):
|
||||
events = list(calendars.issue_165_missing_event.walk('VEVENT'))
|
||||
events = list(calendars.issue_165_missing_event.walk("VEVENT"))
|
||||
assert len(events) == 1, "There was an event missing from the parsed events' list."
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
'''Issue #168 - Parsing invalid icalendars fails without any warning
|
||||
"""Issue #168 - Parsing invalid icalendars fails without any warning
|
||||
|
||||
https://github.com/collective/icalendar/issues/168
|
||||
"""
|
||||
|
||||
https://github.com/collective/icalendar/issues/168
|
||||
'''
|
||||
|
||||
def test_issue_168_parsing_inavlid_calendars_no_warning(calendars):
|
||||
expected_error = (None, "Content line could not be parsed into parts: 'X-APPLE-RADIUS=49.91307046514149': X-APPLE-RADIUS=49.91307046514149")
|
||||
assert expected_error in calendars.issue_168_input.walk('VEVENT')[0].errors
|
||||
assert calendars.issue_168_input.to_ical() == calendars.issue_168_expected_output.raw_ics
|
||||
expected_error = (
|
||||
None,
|
||||
"Content line could not be parsed into parts: 'X-APPLE-RADIUS=49.91307046514149': X-APPLE-RADIUS=49.91307046514149",
|
||||
)
|
||||
assert expected_error in calendars.issue_168_input.walk("VEVENT")[0].errors
|
||||
assert (
|
||||
calendars.issue_168_input.to_ical()
|
||||
== calendars.issue_168_expected_output.raw_ics
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
'''Issue #27 - multiple periods
|
||||
"""Issue #27 - multiple periods
|
||||
|
||||
https://github.com/collective/icalendar/issues/27
|
||||
"""
|
||||
|
||||
https://github.com/collective/icalendar/issues/27
|
||||
'''
|
||||
|
||||
def test_issue_27_multiple_periods(calendars):
|
||||
free_busy = list(calendars.issue_27_multiple_periods_in_freebusy_multiple_freebusies.walk('VFREEBUSY'))[0]
|
||||
free_busy_period = free_busy['freebusy']
|
||||
print(free_busy['freebusy'])
|
||||
equivalent_way_of_defining_free_busy = list(calendars.issue_27_multiple_periods_in_freebusy_one_freebusy.walk('VFREEBUSY'))[0]
|
||||
free_busy_period_equivalent = equivalent_way_of_defining_free_busy['freebusy']
|
||||
free_busy = list(
|
||||
calendars.issue_27_multiple_periods_in_freebusy_multiple_freebusies.walk(
|
||||
"VFREEBUSY"
|
||||
)
|
||||
)[0]
|
||||
free_busy_period = free_busy["freebusy"]
|
||||
print(free_busy["freebusy"])
|
||||
equivalent_way_of_defining_free_busy = list(
|
||||
calendars.issue_27_multiple_periods_in_freebusy_one_freebusy.walk("VFREEBUSY")
|
||||
)[0]
|
||||
free_busy_period_equivalent = equivalent_way_of_defining_free_busy["freebusy"]
|
||||
assert free_busy_period == free_busy_period_equivalent
|
||||
|
||||
|
|
|
|||
|
|
@ -8,20 +8,25 @@ Example:
|
|||
DTSTART;VALUE=DATE-TIME:20190616T050000Z
|
||||
equals DTSTART:20190616T050000Z
|
||||
"""
|
||||
import pytest
|
||||
from icalendar import Event
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
@pytest.mark.parametrize("attr", [
|
||||
"DTSTART",
|
||||
"DTEND",
|
||||
"DTSTAMP",
|
||||
])
|
||||
from icalendar import Event
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"attr",
|
||||
[
|
||||
"DTSTART",
|
||||
"DTEND",
|
||||
"DTSTAMP",
|
||||
],
|
||||
)
|
||||
def test_datetime_in_event(attr):
|
||||
"""Check that the "VALUE=DATE-TIME" is absent because not needed."""
|
||||
event = Event()
|
||||
event.add(attr, datetime(2022, 10, 13, 9, 16, 42))
|
||||
ics = event.to_ical()
|
||||
assert b"VALUE=DATE-TIME" not in ics
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from icalendar import Calendar, Event
|
|||
def test_issue_322_single_string_split_into_multiple_categories(calendars):
|
||||
calendar = Calendar()
|
||||
event = Event()
|
||||
event.add('summary', 'Event with bare string as argument for categories')
|
||||
event.add('categories', "Lecture")
|
||||
event.add("summary", "Event with bare string as argument for categories")
|
||||
event.add("categories", "Lecture")
|
||||
calendar.add_component(event)
|
||||
assert calendar.to_ical() == calendars.issue_322_expected_calendar.raw_ics
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ see https://github.com/collective/icalendar/issues/348
|
|||
def test_calendar_can_be_parsed_correctly(calendars):
|
||||
"""Exception when there's no ':' when parsing value #348
|
||||
|
||||
see https://github.com/collective/icalendar/issues/348
|
||||
see https://github.com/collective/icalendar/issues/348
|
||||
"""
|
||||
freebusy = calendars.issue_348_exception_parsing_value.walk("VFREEBUSY")[0]
|
||||
assert freebusy["ORGANIZER"].params["CN"] == "Sixt SE"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
'''Issue #350 - Ignore X-... properties also at end of file?
|
||||
"""Issue #350 - Ignore X-... properties also at end of file?
|
||||
|
||||
https://github.com/collective/icalendar/issues/350
|
||||
"""
|
||||
|
||||
https://github.com/collective/icalendar/issues/350
|
||||
'''
|
||||
|
||||
def test_issue_350(calendars):
|
||||
calendar = list(calendars.issue_350.walk('X-COMMENT'))
|
||||
calendar = list(calendars.issue_350.walk("X-COMMENT"))
|
||||
assert len(calendar) == 0, "X-COMMENT at the end of the file was parsed"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
from icalendar import Event, vBoolean, vCalAddress
|
||||
|
||||
|
||||
def test_vBoolean_can_be_used_as_parameter_issue_500(events):
|
||||
'''https://github.com/collective/icalendar/issues/500'''
|
||||
attendee = vCalAddress('mailto:someone@example.com')
|
||||
attendee.params['rsvp'] = vBoolean(True)
|
||||
"""https://github.com/collective/icalendar/issues/500"""
|
||||
attendee = vCalAddress("mailto:someone@example.com")
|
||||
attendee.params["rsvp"] = vBoolean(True)
|
||||
event = Event()
|
||||
event.add('attendee', attendee)
|
||||
event.add("attendee", attendee)
|
||||
assert event.to_ical() == events.event_with_rsvp.raw_ics
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ making its behavior unexpected.
|
|||
|
||||
see https://github.com/collective/icalendar/issues/557"""
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
from icalendar.cal import Component
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""An example with multiple VCALENDAR components"""
|
||||
from icalendar.prop import vText
|
||||
|
||||
from icalendar.prop import vText
|
||||
|
||||
|
||||
def test_multiple(calendars):
|
||||
|
|
@ -9,6 +9,8 @@ def test_multiple(calendars):
|
|||
cals = calendars.multiple.multiple_calendar_components
|
||||
|
||||
assert len(cals) == 2
|
||||
assert [comp.name for comp in cals[0].walk()] == ['VCALENDAR', 'VEVENT']
|
||||
assert [comp.name for comp in cals[1].walk()] == ['VCALENDAR', 'VEVENT', 'VEVENT']
|
||||
assert cals[0]['prodid'] == vText('-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN')
|
||||
assert [comp.name for comp in cals[0].walk()] == ["VCALENDAR", "VEVENT"]
|
||||
assert [comp.name for comp in cals[1].walk()] == ["VCALENDAR", "VEVENT", "VEVENT"]
|
||||
assert cals[0]["prodid"] == vText(
|
||||
"-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
"""This file collects errors that the OSS FUZZ build has found."""
|
||||
|
||||
from datetime import time
|
||||
from icalendar import Calendar
|
||||
from icalendar.prop import vDDDLists
|
||||
|
||||
import pytest
|
||||
|
||||
from icalendar import Calendar
|
||||
from icalendar.prop import vDDDLists
|
||||
|
||||
|
||||
def test_stack_is_empty():
|
||||
"""If we get passed an invalid string, we expect to get a ValueError."""
|
||||
|
|
@ -15,4 +17,4 @@ def test_stack_is_empty():
|
|||
def test_vdd_list_type_mismatch():
|
||||
"""If we pass in a string type, we expect it to be converted to bytes"""
|
||||
vddd_list = vDDDLists([time(hour=6, minute=6, second=6)])
|
||||
assert vddd_list.to_ical() == b'060606'
|
||||
assert vddd_list.to_ical() == b"060606"
|
||||
|
|
|
|||
|
|
@ -1,194 +1,257 @@
|
|||
'''Tests checking that parsing works'''
|
||||
import pytest
|
||||
"""Tests checking that parsing works"""
|
||||
|
||||
import base64
|
||||
from icalendar import Calendar, vRecur, vBinary, Event
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from icalendar import Calendar, Event, vBinary, vRecur
|
||||
from icalendar.parser import Contentline, Parameters, unescape_char
|
||||
|
||||
@pytest.mark.parametrize('calendar_name', [
|
||||
# Issue #178 - A component with an unknown/invalid name is represented
|
||||
# as one of the known components, the information about the original
|
||||
# component name is lost.
|
||||
# https://github.com/collective/icalendar/issues/178 https://github.com/collective/icalendar/pull/180
|
||||
# Parsing of a nonstandard component
|
||||
'issue_178_component_with_invalid_name_represented',
|
||||
# Nonstandard component inside other components, also has properties
|
||||
'issue_178_custom_component_inside_other',
|
||||
# Nonstandard component is able to contain other components
|
||||
'issue_178_custom_component_contains_other',
|
||||
])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"calendar_name",
|
||||
[
|
||||
# Issue #178 - A component with an unknown/invalid name is represented
|
||||
# as one of the known components, the information about the original
|
||||
# component name is lost.
|
||||
# https://github.com/collective/icalendar/issues/178 https://github.com/collective/icalendar/pull/180
|
||||
# Parsing of a nonstandard component
|
||||
"issue_178_component_with_invalid_name_represented",
|
||||
# Nonstandard component inside other components, also has properties
|
||||
"issue_178_custom_component_inside_other",
|
||||
# Nonstandard component is able to contain other components
|
||||
"issue_178_custom_component_contains_other",
|
||||
],
|
||||
)
|
||||
def test_calendar_to_ical_is_inverse_of_from_ical(calendars, calendar_name):
|
||||
calendar = getattr(calendars, calendar_name)
|
||||
assert calendar.to_ical().splitlines() == calendar.raw_ics.splitlines()
|
||||
assert calendar.to_ical() == calendar.raw_ics
|
||||
|
||||
@pytest.mark.parametrize('raw_content_line, expected_output', [
|
||||
# Issue #142 - Multivalued parameters. This is needed for VCard 3.0.
|
||||
# see https://github.com/collective/icalendar/pull/142
|
||||
('TEL;TYPE=HOME,VOICE:000000000', ('TEL', Parameters({'TYPE': ['HOME', 'VOICE']}), '000000000')),
|
||||
# Issue #143 - Allow dots in property names. Another vCard related issue.
|
||||
# see https://github.com/collective/icalendar/pull/143
|
||||
('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR:;;This is the Adress 08; Some City;;12345;Germany', \
|
||||
('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR', \
|
||||
Parameters(),\
|
||||
';;This is the Adress 08; Some City;;12345;Germany')),
|
||||
('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL:', \
|
||||
('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL', \
|
||||
Parameters(), \
|
||||
''))
|
||||
])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("raw_content_line", "expected_output"),
|
||||
[
|
||||
# Issue #142 - Multivalued parameters. This is needed for VCard 3.0.
|
||||
# see https://github.com/collective/icalendar/pull/142
|
||||
(
|
||||
"TEL;TYPE=HOME,VOICE:000000000",
|
||||
("TEL", Parameters({"TYPE": ["HOME", "VOICE"]}), "000000000"),
|
||||
),
|
||||
# Issue #143 - Allow dots in property names. Another vCard related issue.
|
||||
# see https://github.com/collective/icalendar/pull/143
|
||||
(
|
||||
"ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR:;;This is the Adress 08; Some City;;12345;Germany",
|
||||
(
|
||||
"ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR",
|
||||
Parameters(),
|
||||
";;This is the Adress 08; Some City;;12345;Germany",
|
||||
),
|
||||
),
|
||||
(
|
||||
"ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL:",
|
||||
(
|
||||
"ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL",
|
||||
Parameters(),
|
||||
"",
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_content_lines_parsed_properly(raw_content_line, expected_output):
|
||||
assert Contentline.from_ical(raw_content_line).parts() == expected_output
|
||||
|
||||
|
||||
@pytest.mark.parametrize('timezone_info', [
|
||||
# General timezone aware dates in ical string
|
||||
(b'DTSTART;TZID=America/New_York:20130907T120000'),
|
||||
(b'DTEND;TZID=America/New_York:20130907T170000'),
|
||||
# Specific timezone aware exdates in ical string
|
||||
(b'EXDATE;TZID=America/New_York:20131012T120000'),
|
||||
(b'EXDATE;TZID=America/New_York:20131011T120000')
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"timezone_info",
|
||||
[
|
||||
# General timezone aware dates in ical string
|
||||
(b"DTSTART;TZID=America/New_York:20130907T120000"),
|
||||
(b"DTEND;TZID=America/New_York:20130907T170000"),
|
||||
# Specific timezone aware exdates in ical string
|
||||
(b"EXDATE;TZID=America/New_York:20131012T120000"),
|
||||
(b"EXDATE;TZID=America/New_York:20131011T120000"),
|
||||
],
|
||||
)
|
||||
def test_timezone_info_present_in_ical_issue_112(events, timezone_info):
|
||||
'''Issue #112 - No timezone info on EXDATE
|
||||
"""Issue #112 - No timezone info on EXDATE
|
||||
|
||||
https://github.com/collective/icalendar/issues/112
|
||||
'''
|
||||
timezone_info in events.issue_112_missing_tzinfo_on_exdate.to_ical()
|
||||
"""
|
||||
assert timezone_info in events.issue_112_missing_tzinfo_on_exdate.to_ical()
|
||||
|
||||
|
||||
def test_timezone_name_parsed_issue_112(events):
|
||||
'''Issue #112 - No timezone info on EXDATE
|
||||
"""Issue #112 - No timezone info on EXDATE
|
||||
|
||||
https://github.com/collective/icalendar/issues/112
|
||||
'''
|
||||
assert events.issue_112_missing_tzinfo_on_exdate['exdate'][0].dts[0].dt.tzname() == 'EDT'
|
||||
"""
|
||||
assert (
|
||||
events.issue_112_missing_tzinfo_on_exdate["exdate"][0].dts[0].dt.tzname()
|
||||
== "EDT"
|
||||
)
|
||||
|
||||
|
||||
def test_issue_157_removes_trailing_semicolon(events):
|
||||
'''Issue #157 - Recurring rules and trailing semicolons
|
||||
"""Issue #157 - Recurring rules and trailing semicolons
|
||||
|
||||
https://github.com/collective/icalendar/pull/157
|
||||
'''
|
||||
"""
|
||||
recur = events.issue_157_removes_trailing_semicolon.decoded("RRULE")
|
||||
assert isinstance(recur, vRecur)
|
||||
assert recur.to_ical() == b'FREQ=YEARLY;BYDAY=1SU;BYMONTH=11'
|
||||
assert recur.to_ical() == b"FREQ=YEARLY;BYDAY=1SU;BYMONTH=11"
|
||||
|
||||
@pytest.mark.parametrize('event_name', [
|
||||
# https://github.com/collective/icalendar/pull/100
|
||||
('issue_100_transformed_doctests_into_unittests'),
|
||||
('issue_184_broken_representation_of_period'),
|
||||
# PERIOD should be put back into shape
|
||||
'issue_156_RDATE_with_PERIOD',
|
||||
'issue_156_RDATE_with_PERIOD_list',
|
||||
'event_with_unicode_organizer',
|
||||
])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"event_name",
|
||||
[
|
||||
# https://github.com/collective/icalendar/pull/100
|
||||
("issue_100_transformed_doctests_into_unittests"),
|
||||
("issue_184_broken_representation_of_period"),
|
||||
# PERIOD should be put back into shape
|
||||
"issue_156_RDATE_with_PERIOD",
|
||||
"issue_156_RDATE_with_PERIOD_list",
|
||||
"event_with_unicode_organizer",
|
||||
],
|
||||
)
|
||||
def test_event_to_ical_is_inverse_of_from_ical(events, event_name):
|
||||
"""Make sure that an event's ICS is equal to the ICS it was made from."""
|
||||
event = events[event_name]
|
||||
assert event.to_ical().splitlines() == event.raw_ics.splitlines()
|
||||
assert event.to_ical() == event.raw_ics
|
||||
|
||||
|
||||
def test_decode_rrule_attribute_error_issue_70(events):
|
||||
# Issue #70 - e.decode("RRULE") causes Attribute Error
|
||||
# see https://github.com/collective/icalendar/issues/70
|
||||
recur = events.issue_70_rrule_causes_attribute_error.decoded('RRULE')
|
||||
recur = events.issue_70_rrule_causes_attribute_error.decoded("RRULE")
|
||||
assert isinstance(recur, vRecur)
|
||||
assert recur.to_ical() == b'FREQ=WEEKLY;UNTIL=20070619T225959;INTERVAL=1'
|
||||
assert recur.to_ical() == b"FREQ=WEEKLY;UNTIL=20070619T225959;INTERVAL=1"
|
||||
|
||||
|
||||
def test_description_parsed_properly_issue_53(events):
|
||||
'''Issue #53 - Parsing failure on some descriptions?
|
||||
"""Issue #53 - Parsing failure on some descriptions?
|
||||
|
||||
https://github.com/collective/icalendar/issues/53
|
||||
'''
|
||||
assert b'July 12 at 6:30 PM' in events.issue_53_description_parsed_properly['DESCRIPTION'].to_ical()
|
||||
"""
|
||||
assert (
|
||||
b"July 12 at 6:30 PM"
|
||||
in events.issue_53_description_parsed_properly["DESCRIPTION"].to_ical()
|
||||
)
|
||||
|
||||
|
||||
def test_raises_value_error_for_properties_without_parent_pull_179():
|
||||
'''Found an issue where from_ical() would raise IndexError for
|
||||
properties without parent components.
|
||||
"""Found an issue where from_ical() would raise IndexError for
|
||||
properties without parent components.
|
||||
|
||||
https://github.com/collective/icalendar/pull/179
|
||||
"""
|
||||
with pytest.raises(ValueError):
|
||||
Calendar.from_ical("VERSION:2.0")
|
||||
|
||||
https://github.com/collective/icalendar/pull/179
|
||||
'''
|
||||
with pytest.raises(ValueError):
|
||||
Calendar.from_ical('VERSION:2.0')
|
||||
|
||||
def test_tzid_parsed_properly_issue_53(timezones):
|
||||
'''Issue #53 - Parsing failure on some descriptions?
|
||||
"""Issue #53 - Parsing failure on some descriptions?
|
||||
|
||||
https://github.com/collective/icalendar/issues/53
|
||||
'''
|
||||
assert timezones.issue_53_tzid_parsed_properly['tzid'].to_ical() == b'America/New_York'
|
||||
"""
|
||||
assert (
|
||||
timezones.issue_53_tzid_parsed_properly["tzid"].to_ical() == b"America/New_York"
|
||||
)
|
||||
|
||||
|
||||
def test_timezones_to_ical_is_inverse_of_from_ical(timezones):
|
||||
'''Issue #55 - Parse error on utc-offset with seconds value
|
||||
see https://github.com/collective/icalendar/issues/55'''
|
||||
timezone = timezones['issue_55_parse_error_on_utc_offset_with_seconds']
|
||||
"""Issue #55 - Parse error on utc-offset with seconds value
|
||||
see https://github.com/collective/icalendar/issues/55"""
|
||||
timezone = timezones["issue_55_parse_error_on_utc_offset_with_seconds"]
|
||||
assert timezone.to_ical() == timezone.raw_ics
|
||||
|
||||
@pytest.mark.parametrize('date, expected_output', [
|
||||
(datetime(2012, 7, 16, 0, 0, 0), b'DTSTART:20120716T000000Z'),
|
||||
(datetime(2021, 11, 17, 15, 9, 15), b'DTSTART:20211117T150915Z')
|
||||
])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("date", "expected_output"),
|
||||
[
|
||||
(datetime(2012, 7, 16, 0, 0, 0), b"DTSTART:20120716T000000Z"),
|
||||
(datetime(2021, 11, 17, 15, 9, 15), b"DTSTART:20211117T150915Z"),
|
||||
],
|
||||
)
|
||||
def test_no_tzid_when_utc(utc, date, expected_output):
|
||||
'''Issue #58 - TZID on UTC DATE-TIMEs
|
||||
"""Issue #58 - TZID on UTC DATE-TIMEs
|
||||
Issue #335 - UTC timezone identification is broken
|
||||
|
||||
https://github.com/collective/icalendar/issues/58
|
||||
https://github.com/collective/icalendar/issues/335
|
||||
'''
|
||||
"""
|
||||
# According to RFC 5545: "The TZID property parameter MUST NOT be
|
||||
# applied to DATE-TIME or TIME properties whose time values are
|
||||
# specified in UTC.
|
||||
date = date.replace(tzinfo=utc)
|
||||
event = Event()
|
||||
event.add('dtstart', date)
|
||||
event.add("dtstart", date)
|
||||
assert expected_output in event.to_ical()
|
||||
|
||||
|
||||
def test_vBinary_base64_encoded_issue_82():
|
||||
'''Issue #82 - vBinary __repr__ called rather than to_ical from
|
||||
"""Issue #82 - vBinary __repr__ called rather than to_ical from
|
||||
container types
|
||||
https://github.com/collective/icalendar/issues/82
|
||||
'''
|
||||
b = vBinary('text')
|
||||
b.params['FMTTYPE'] = 'text/plain'
|
||||
assert b.to_ical() == base64.b64encode(b'text')
|
||||
"""
|
||||
b = vBinary("text")
|
||||
b.params["FMTTYPE"] = "text/plain"
|
||||
assert b.to_ical() == base64.b64encode(b"text")
|
||||
|
||||
|
||||
def test_creates_event_with_base64_encoded_attachment_issue_82(events):
|
||||
'''Issue #82 - vBinary __repr__ called rather than to_ical from
|
||||
"""Issue #82 - vBinary __repr__ called rather than to_ical from
|
||||
container types
|
||||
https://github.com/collective/icalendar/issues/82
|
||||
'''
|
||||
b = vBinary('text')
|
||||
b.params['FMTTYPE'] = 'text/plain'
|
||||
"""
|
||||
b = vBinary("text")
|
||||
b.params["FMTTYPE"] = "text/plain"
|
||||
event = Event()
|
||||
event.add('ATTACH', b)
|
||||
event.add("ATTACH", b)
|
||||
assert event.to_ical() == events.issue_82_expected_output.raw_ics
|
||||
|
||||
@pytest.mark.parametrize('calendar_name', [
|
||||
# Issue #466 - [BUG] TZID timezone is ignored when forward-slash is used
|
||||
# https://github.com/collective/icalendar/issues/466
|
||||
'issue_466_respect_unique_timezone',
|
||||
'issue_466_convert_tzid_with_slash'
|
||||
])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"calendar_name",
|
||||
[
|
||||
# Issue #466 - [BUG] TZID timezone is ignored when forward-slash is used
|
||||
# https://github.com/collective/icalendar/issues/466
|
||||
"issue_466_respect_unique_timezone",
|
||||
"issue_466_convert_tzid_with_slash",
|
||||
],
|
||||
)
|
||||
def test_handles_unique_tzid(calendars, in_timezone, calendar_name):
|
||||
calendar = calendars[calendar_name]
|
||||
event = calendar.walk('VEVENT')[0]
|
||||
event = calendar.walk("VEVENT")[0]
|
||||
print(vars(event))
|
||||
start_dt = event['dtstart'].dt
|
||||
end_dt = event['dtend'].dt
|
||||
assert start_dt == in_timezone(datetime(2022, 10, 21, 20, 0, 0), 'Europe/Stockholm')
|
||||
assert end_dt == in_timezone(datetime(2022, 10, 21, 21, 0, 0), 'Europe/Stockholm')
|
||||
start_dt = event["dtstart"].dt
|
||||
end_dt = event["dtend"].dt
|
||||
assert start_dt == in_timezone(datetime(2022, 10, 21, 20, 0, 0), "Europe/Stockholm")
|
||||
assert end_dt == in_timezone(datetime(2022, 10, 21, 21, 0, 0), "Europe/Stockholm")
|
||||
|
||||
@pytest.mark.parametrize('event_name, expected_cn, expected_ics', [
|
||||
('event_with_escaped_characters', r'that, that; %th%%at%\ that:', 'это, то; that\\ %th%%at%:'),
|
||||
('event_with_escaped_character1', r'Society, 2014', 'that'),
|
||||
('event_with_escaped_character2', r'Society\ 2014', 'that'),
|
||||
('event_with_escaped_character3', r'Society; 2014', 'that'),
|
||||
('event_with_escaped_character4', r'Society: 2014', 'that'),
|
||||
])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("event_name", "expected_cn", "expected_ics"),
|
||||
[
|
||||
(
|
||||
"event_with_escaped_characters",
|
||||
r"that, that; %th%%at%\ that:",
|
||||
"это, то; that\\ %th%%at%:",
|
||||
),
|
||||
("event_with_escaped_character1", r"Society, 2014", "that"),
|
||||
("event_with_escaped_character2", r"Society\ 2014", "that"),
|
||||
("event_with_escaped_character3", r"Society; 2014", "that"),
|
||||
("event_with_escaped_character4", r"Society: 2014", "that"),
|
||||
],
|
||||
)
|
||||
def test_escaped_characters_read(event_name, expected_cn, expected_ics, events):
|
||||
event = events[event_name]
|
||||
assert event['ORGANIZER'].params['CN'] == expected_cn
|
||||
assert event['ORGANIZER'].to_ical() == expected_ics.encode('utf-8')
|
||||
|
||||
assert event["ORGANIZER"].params["CN"] == expected_cn
|
||||
assert event["ORGANIZER"].to_ical() == expected_ics.encode("utf-8")
|
||||
|
||||
|
||||
def test_unescape_char():
|
||||
assert unescape_char(b'123') == b'123'
|
||||
assert unescape_char(b"123") == b"123"
|
||||
assert unescape_char(b"\\n") == b"\n"
|
||||
|
|
|
|||
|
|
@ -4,29 +4,62 @@ See
|
|||
- https://github.com/collective/icalendar/issues/156
|
||||
- https://github.com/pimutils/khal/issues/152#issuecomment-933635248
|
||||
"""
|
||||
import pytest
|
||||
from icalendar.prop import vDDDTypes
|
||||
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
@pytest.mark.parametrize("calname,tzname,index,period_string", [
|
||||
("issue_156_RDATE_with_PERIOD_TZID_khal_2", "Europe/Berlin", 0, "20211101T160000/20211101T163000"),
|
||||
("issue_156_RDATE_with_PERIOD_TZID_khal_2", "Europe/Berlin", 1, "20211206T160000/20211206T163000"),
|
||||
("issue_156_RDATE_with_PERIOD_TZID_khal_2", "Europe/Berlin", 2, "20220103T160000/20220103T163000"),
|
||||
("issue_156_RDATE_with_PERIOD_TZID_khal_2", "Europe/Berlin", 3, "20220207T160000/20220207T163000"),
|
||||
] + [
|
||||
("issue_156_RDATE_with_PERIOD_TZID_khal", "America/Chicago", i, period)
|
||||
for i, period in enumerate(("20180327T080000/20180327T0"
|
||||
"90000,20180403T080000/20180403T090000,20180410T080000/20180410T090000,2018"
|
||||
"0417T080000/20180417T090000,20180424T080000/20180424T090000,20180501T08000"
|
||||
"0/20180501T090000,20180508T080000/20180508T090000,20180515T080000/20180515"
|
||||
"T090000,20180522T080000/20180522T090000,20180529T080000/20180529T090000,20"
|
||||
"180605T080000/20180605T090000,20180612T080000/20180612T090000,20180619T080"
|
||||
"000/20180619T090000,20180626T080000/20180626T090000,20180703T080000/201807"
|
||||
"03T090000,20180710T080000/20180710T090000,20180717T080000/20180717T090000,"
|
||||
"20180724T080000/20180724T090000,20180731T080000/20180731T090000").split(","))
|
||||
])
|
||||
def test_issue_156_period_list_in_rdate(calendars, calname, tzname, index, period_string):
|
||||
from icalendar.prop import vDDDTypes
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("calname", "tzname", "index", "period_string"),
|
||||
[
|
||||
(
|
||||
"issue_156_RDATE_with_PERIOD_TZID_khal_2",
|
||||
"Europe/Berlin",
|
||||
0,
|
||||
"20211101T160000/20211101T163000",
|
||||
),
|
||||
(
|
||||
"issue_156_RDATE_with_PERIOD_TZID_khal_2",
|
||||
"Europe/Berlin",
|
||||
1,
|
||||
"20211206T160000/20211206T163000",
|
||||
),
|
||||
(
|
||||
"issue_156_RDATE_with_PERIOD_TZID_khal_2",
|
||||
"Europe/Berlin",
|
||||
2,
|
||||
"20220103T160000/20220103T163000",
|
||||
),
|
||||
(
|
||||
"issue_156_RDATE_with_PERIOD_TZID_khal_2",
|
||||
"Europe/Berlin",
|
||||
3,
|
||||
"20220207T160000/20220207T163000",
|
||||
),
|
||||
]
|
||||
+ [
|
||||
("issue_156_RDATE_with_PERIOD_TZID_khal", "America/Chicago", i, period)
|
||||
for i, period in enumerate(
|
||||
(
|
||||
"20180327T080000/20180327T0"
|
||||
"90000,20180403T080000/20180403T090000,20180410T080000/20180410T090000,2018"
|
||||
"0417T080000/20180417T090000,20180424T080000/20180424T090000,20180501T08000"
|
||||
"0/20180501T090000,20180508T080000/20180508T090000,20180515T080000/20180515"
|
||||
"T090000,20180522T080000/20180522T090000,20180529T080000/20180529T090000,20"
|
||||
"180605T080000/20180605T090000,20180612T080000/20180612T090000,20180619T080"
|
||||
"000/20180619T090000,20180626T080000/20180626T090000,20180703T080000/201807"
|
||||
"03T090000,20180710T080000/20180710T090000,20180717T080000/20180717T090000,"
|
||||
"20180724T080000/20180724T090000,20180731T080000/20180731T090000"
|
||||
).split(",")
|
||||
)
|
||||
],
|
||||
)
|
||||
def test_issue_156_period_list_in_rdate(
|
||||
calendars, calname, tzname, index, period_string
|
||||
):
|
||||
"""Check items in a list of period values."""
|
||||
calendar = calendars[calname]
|
||||
rdate = calendar.walk("vevent")[0]["rdate"]
|
||||
|
|
@ -58,5 +91,7 @@ def test_tzid_is_part_of_the_period_values(calendars, tzp):
|
|||
"""The TZID should be set in the datetime."""
|
||||
event = list(calendars.period_with_timezone.walk("VEVENT"))[0]
|
||||
start, end = event["RDATE"].dts[0].dt
|
||||
assert start == tzp.localize(datetime.datetime(2023, 12, 13, 12), "America/Vancouver")
|
||||
assert start == tzp.localize(
|
||||
datetime.datetime(2023, 12, 13, 12), "America/Vancouver"
|
||||
)
|
||||
assert end == tzp.localize(datetime.datetime(2023, 12, 13, 15), "America/Vancouver")
|
||||
|
|
|
|||
|
|
@ -1,94 +1,129 @@
|
|||
import pytest
|
||||
from icalendar import Calendar, Event, Parameters, vCalAddress
|
||||
|
||||
import re
|
||||
|
||||
@pytest.mark.parametrize('parameter, expected', [
|
||||
# Simple parameter:value pair
|
||||
(Parameters(parameter1='Value1'), b'PARAMETER1=Value1'),
|
||||
# Parameter with list of values must be separated by comma
|
||||
(Parameters({'parameter1': ['Value1', 'Value2']}), b'PARAMETER1=Value1,Value2'),
|
||||
# Multiple parameters must be separated by a semicolon
|
||||
(Parameters({'RSVP': 'TRUE', 'ROLE': 'REQ-PARTICIPANT'}), b'ROLE=REQ-PARTICIPANT;RSVP=TRUE'),
|
||||
# Parameter values containing ',;:' must be double quoted
|
||||
(Parameters({'ALTREP': 'http://www.wiz.org'}), b'ALTREP="http://www.wiz.org"'),
|
||||
# list items must be quoted separately
|
||||
(Parameters({'MEMBER': ['MAILTO:projectA@host.com',
|
||||
'MAILTO:projectB@host.com']}),
|
||||
b'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"'),
|
||||
(Parameters({'parameter1': 'Value1',
|
||||
'parameter2': ['Value2', 'Value3'],
|
||||
'ALTREP': ['http://www.wiz.org', 'value4']}),
|
||||
b'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3'),
|
||||
# Including empty strings
|
||||
(Parameters({'PARAM': ''}), b'PARAM='),
|
||||
# We can also parse parameter strings
|
||||
(Parameters({'MEMBER': ['MAILTO:projectA@host.com',
|
||||
'MAILTO:projectB@host.com']}),
|
||||
b'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"'),
|
||||
# We can also parse parameter strings
|
||||
(Parameters({'PARAMETER1': 'Value1',
|
||||
'ALTREP': ['http://www.wiz.org', 'value4'],
|
||||
'PARAMETER2': ['Value2', 'Value3']}),
|
||||
b'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3'),
|
||||
])
|
||||
import pytest
|
||||
|
||||
from icalendar import Calendar, Event, Parameters, vCalAddress
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("parameter", "expected"),
|
||||
[
|
||||
# Simple parameter:value pair
|
||||
(Parameters(parameter1="Value1"), b"PARAMETER1=Value1"),
|
||||
# Parameter with list of values must be separated by comma
|
||||
(Parameters({"parameter1": ["Value1", "Value2"]}), b"PARAMETER1=Value1,Value2"),
|
||||
# Multiple parameters must be separated by a semicolon
|
||||
(
|
||||
Parameters({"RSVP": "TRUE", "ROLE": "REQ-PARTICIPANT"}),
|
||||
b"ROLE=REQ-PARTICIPANT;RSVP=TRUE",
|
||||
),
|
||||
# Parameter values containing ',;:' must be double quoted
|
||||
(Parameters({"ALTREP": "http://www.wiz.org"}), b'ALTREP="http://www.wiz.org"'),
|
||||
# list items must be quoted separately
|
||||
(
|
||||
Parameters(
|
||||
{"MEMBER": ["MAILTO:projectA@host.com", "MAILTO:projectB@host.com"]}
|
||||
),
|
||||
b'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"',
|
||||
),
|
||||
(
|
||||
Parameters(
|
||||
{
|
||||
"parameter1": "Value1",
|
||||
"parameter2": ["Value2", "Value3"],
|
||||
"ALTREP": ["http://www.wiz.org", "value4"],
|
||||
}
|
||||
),
|
||||
b'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3',
|
||||
),
|
||||
# Including empty strings
|
||||
(Parameters({"PARAM": ""}), b"PARAM="),
|
||||
# We can also parse parameter strings
|
||||
(
|
||||
Parameters(
|
||||
{"MEMBER": ["MAILTO:projectA@host.com", "MAILTO:projectB@host.com"]}
|
||||
),
|
||||
b'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"',
|
||||
),
|
||||
# We can also parse parameter strings
|
||||
(
|
||||
Parameters(
|
||||
{
|
||||
"PARAMETER1": "Value1",
|
||||
"ALTREP": ["http://www.wiz.org", "value4"],
|
||||
"PARAMETER2": ["Value2", "Value3"],
|
||||
}
|
||||
),
|
||||
b'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3',
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_parameter_to_ical_is_inverse_of_from_ical(parameter, expected):
|
||||
assert parameter.to_ical() == expected
|
||||
assert Parameters.from_ical(expected.decode('utf-8')) == parameter
|
||||
assert Parameters.from_ical(expected.decode("utf-8")) == parameter
|
||||
|
||||
|
||||
def test_parse_parameter_string_without_quotes():
|
||||
assert Parameters.from_ical('PARAM1=Value 1;PARA2=Value 2') == Parameters({'PARAM1': 'Value 1', 'PARA2': 'Value 2'})
|
||||
assert Parameters.from_ical("PARAM1=Value 1;PARA2=Value 2") == Parameters(
|
||||
{"PARAM1": "Value 1", "PARA2": "Value 2"}
|
||||
)
|
||||
|
||||
|
||||
def test_parametr_is_case_insensitive():
|
||||
parameter = Parameters(parameter1='Value1')
|
||||
assert parameter['parameter1'] == parameter['PARAMETER1'] == parameter['PaRaMeTer1']
|
||||
parameter = Parameters(parameter1="Value1")
|
||||
assert parameter["parameter1"] == parameter["PARAMETER1"] == parameter["PaRaMeTer1"]
|
||||
|
||||
|
||||
def test_parameter_keys_are_uppercase():
|
||||
parameter = Parameters(parameter1='Value1')
|
||||
assert list(parameter.keys()) == ['PARAMETER1']
|
||||
parameter = Parameters(parameter1="Value1")
|
||||
assert list(parameter.keys()) == ["PARAMETER1"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('cn_param, cn_quoted', [
|
||||
# not double-quoted
|
||||
('Aramis', 'Aramis'),
|
||||
# if a space is present - enclose in double quotes
|
||||
('Aramis Alameda', '"Aramis Alameda"'),
|
||||
# a single quote in parameter value - double quote the value
|
||||
('Aramis d\'Alameda', '"Aramis d\'Alameda"'),
|
||||
('Арамис д\'Аламеда', '"Арамис д\'Аламеда"'),
|
||||
# double quote is replaced with single quote
|
||||
('Aramis d\"Alameda', '"Aramis d\'Alameda"'),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
("cn_param", "cn_quoted"),
|
||||
[
|
||||
# not double-quoted
|
||||
("Aramis", "Aramis"),
|
||||
# if a space is present - enclose in double quotes
|
||||
("Aramis Alameda", '"Aramis Alameda"'),
|
||||
# a single quote in parameter value - double quote the value
|
||||
("Aramis d'Alameda", '"Aramis d\'Alameda"'),
|
||||
("Арамис д'Аламеда", '"Арамис д\'Аламеда"'),
|
||||
# double quote is replaced with single quote
|
||||
('Aramis d"Alameda', '"Aramis d\'Alameda"'),
|
||||
],
|
||||
)
|
||||
def test_quoting(cn_param, cn_quoted):
|
||||
event = Event()
|
||||
attendee = vCalAddress('test@example.com')
|
||||
attendee.params['CN'] = cn_param
|
||||
event.add('ATTENDEE', attendee)
|
||||
assert f'ATTENDEE;CN={cn_quoted}:test@example.com' in event.to_ical().decode('utf-8')
|
||||
attendee = vCalAddress("test@example.com")
|
||||
attendee.params["CN"] = cn_param
|
||||
event.add("ATTENDEE", attendee)
|
||||
assert f"ATTENDEE;CN={cn_quoted}:test@example.com" in event.to_ical().decode(
|
||||
"utf-8"
|
||||
)
|
||||
|
||||
|
||||
def test_property_params():
|
||||
"""Property parameters with values containing a COLON character, a
|
||||
SEMICOLON character or a COMMA character MUST be placed in quoted
|
||||
text."""
|
||||
cal_address = vCalAddress('mailto:john.doe@example.org')
|
||||
cal_address = vCalAddress("mailto:john.doe@example.org")
|
||||
cal_address.params["CN"] = "Doe, John"
|
||||
ical = Calendar()
|
||||
ical.add('organizer', cal_address)
|
||||
ical.add("organizer", cal_address)
|
||||
|
||||
ical_str = Calendar.to_ical(ical)
|
||||
exp_str = b"""BEGIN:VCALENDAR\r\nORGANIZER;CN="Doe, John":"""\
|
||||
b"""mailto:john.doe@example.org\r\nEND:VCALENDAR\r\n"""
|
||||
exp_str = (
|
||||
b"""BEGIN:VCALENDAR\r\nORGANIZER;CN="Doe, John":"""
|
||||
b"""mailto:john.doe@example.org\r\nEND:VCALENDAR\r\n"""
|
||||
)
|
||||
|
||||
assert ical_str == exp_str
|
||||
|
||||
# other way around: ensure the property parameters can be restored from
|
||||
# an icalendar string.
|
||||
ical2 = Calendar.from_ical(ical_str)
|
||||
assert ical2.get('ORGANIZER').params.get('CN') == 'Doe, John'
|
||||
assert ical2.get("ORGANIZER").params.get("CN") == "Doe, John"
|
||||
|
||||
|
||||
def test_parse_and_access_property_params(calendars):
|
||||
|
|
@ -97,13 +132,13 @@ def test_parse_and_access_property_params(calendars):
|
|||
|
||||
"""
|
||||
event = calendars.property_params.walk("VEVENT")[0]
|
||||
attendee = event['attendee'][0]
|
||||
assert attendee.to_ical() == b'MAILTO:rembrand@xs4all.nl'
|
||||
assert attendee.params.to_ical() == b'CN=RembrandXS;PARTSTAT=NEEDS-ACTION;RSVP=TRUE'
|
||||
assert attendee.params['cn'] == 'RembrandXS'
|
||||
attendee = event["attendee"][0]
|
||||
assert attendee.to_ical() == b"MAILTO:rembrand@xs4all.nl"
|
||||
assert attendee.params.to_ical() == b"CN=RembrandXS;PARTSTAT=NEEDS-ACTION;RSVP=TRUE"
|
||||
assert attendee.params["cn"] == "RembrandXS"
|
||||
|
||||
|
||||
def test_repr():
|
||||
"""Test correct class representation.
|
||||
"""
|
||||
it = Parameters(parameter1='Value1')
|
||||
"""Test correct class representation."""
|
||||
it = Parameters(parameter1="Value1")
|
||||
assert re.match(r"Parameters\({u?'PARAMETER1': u?'Value1'}\)", str(it))
|
||||
|
|
|
|||
|
|
@ -2,19 +2,23 @@
|
|||
|
||||
These are mostly located in icalendar.timezone.
|
||||
"""
|
||||
|
||||
try:
|
||||
import pytz
|
||||
|
||||
from icalendar.timezone.pytz import PYTZ
|
||||
except ImportError:
|
||||
pytz = None
|
||||
from icalendar.timezone.zoneinfo import zoneinfo, ZONEINFO
|
||||
from dateutil.tz.tz import _tzicalvtz
|
||||
import pytest
|
||||
import copy
|
||||
import pickle
|
||||
from dateutil.rrule import rrule, MONTHLY
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from dateutil.rrule import MONTHLY, rrule
|
||||
from dateutil.tz.tz import _tzicalvtz
|
||||
|
||||
from icalendar.timezone.zoneinfo import ZONEINFO, zoneinfo
|
||||
|
||||
if pytz:
|
||||
PYTZ_TIMEZONES = pytz.all_timezones
|
||||
TZP_ = [PYTZ(), ZONEINFO()]
|
||||
|
|
@ -25,17 +29,27 @@ else:
|
|||
NEW_TZP_NAME = ["zoneinfo"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tz_name", PYTZ_TIMEZONES + list(zoneinfo.available_timezones()))
|
||||
@pytest.mark.parametrize(
|
||||
"tz_name", PYTZ_TIMEZONES + list(zoneinfo.available_timezones())
|
||||
)
|
||||
@pytest.mark.parametrize("tzp_", TZP_)
|
||||
def test_timezone_names_are_known(tz_name, tzp_):
|
||||
"""Make sure that all timezones are understood."""
|
||||
if tz_name in ("Factory", "localtime"):
|
||||
pytest.skip()
|
||||
assert tzp_.knows_timezone_id(tz_name), f"{tzp_.__class__.__name__} should know {tz_name}"
|
||||
assert tzp_.knows_timezone_id(
|
||||
tz_name
|
||||
), f"{tzp_.__class__.__name__} should know {tz_name}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("func", [pickle.dumps, copy.copy, copy.deepcopy])
|
||||
@pytest.mark.parametrize("obj", [_tzicalvtz("id"), rrule(freq=MONTHLY, count=4, dtstart=datetime(2028, 10, 1), cache=True)])
|
||||
@pytest.mark.parametrize(
|
||||
"obj",
|
||||
[
|
||||
_tzicalvtz("id"),
|
||||
rrule(freq=MONTHLY, count=4, dtstart=datetime(2028, 10, 1), cache=True),
|
||||
],
|
||||
)
|
||||
def test_can_pickle_timezone(func, tzp, obj):
|
||||
"""Check that we can serialize and copy timezones."""
|
||||
func(obj)
|
||||
|
|
|
|||
|
|
@ -1,22 +1,32 @@
|
|||
from icalendar import Event
|
||||
from datetime import date, datetime
|
||||
|
||||
import pytest
|
||||
|
||||
def test_recurrence_properly_parsed(events):
|
||||
assert events.event_with_recurrence['rrule'] == {'COUNT': [100], 'FREQ': ['DAILY']}
|
||||
from icalendar import Event
|
||||
|
||||
@pytest.mark.parametrize('i, exception_date', [
|
||||
(0, datetime(1996, 4, 2, 1, 0)),
|
||||
(1, datetime(1996, 4, 3, 1, 0)),
|
||||
(2, datetime(1996, 4, 4, 1, 0))
|
||||
])
|
||||
|
||||
def test_recurrence_properly_parsed(events):
|
||||
assert events.event_with_recurrence["rrule"] == {"COUNT": [100], "FREQ": ["DAILY"]}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("i", "exception_date"),
|
||||
[
|
||||
(0, datetime(1996, 4, 2, 1, 0)),
|
||||
(1, datetime(1996, 4, 3, 1, 0)),
|
||||
(2, datetime(1996, 4, 4, 1, 0)),
|
||||
],
|
||||
)
|
||||
def test_exdate_properly_parsed(events, i, exception_date, in_timezone):
|
||||
assert events.event_with_recurrence['exdate'].dts[i].dt == in_timezone(exception_date, 'UTC')
|
||||
assert events.event_with_recurrence["exdate"].dts[i].dt == in_timezone(
|
||||
exception_date, "UTC"
|
||||
)
|
||||
|
||||
|
||||
def test_exdate_properly_marshalled(events):
|
||||
actual = events.event_with_recurrence['exdate'].to_ical()
|
||||
assert actual == b'19960402T010000Z,19960403T010000Z,19960404T010000Z'
|
||||
actual = events.event_with_recurrence["exdate"].to_ical()
|
||||
assert actual == b"19960402T010000Z,19960403T010000Z,19960404T010000Z"
|
||||
|
||||
|
||||
# TODO: DOCUMENT BETTER!
|
||||
# In this case we have multiple EXDATE definitions, one per line.
|
||||
|
|
@ -26,49 +36,95 @@ def test_exdate_properly_marshalled(events):
|
|||
# code has to handle this as list and not blindly expecting to be able
|
||||
# to call event['EXDATE'].to_ical() on it:
|
||||
def test_exdate_formed_from_exdates_on_multiple_lines_is_a_list(events):
|
||||
exdate = events.event_with_recurrence_exdates_on_different_lines['exdate']
|
||||
exdate = events.event_with_recurrence_exdates_on_different_lines["exdate"]
|
||||
assert isinstance(exdate, list)
|
||||
|
||||
@pytest.mark.parametrize('i, exception_date, exception_date_ics', [
|
||||
(0, datetime(2012, 5, 29, 10, 0), b'20120529T100000'),
|
||||
(1, datetime(2012, 4, 3, 10, 0), b'20120403T100000'),
|
||||
(2, datetime(2012, 4, 10, 10, 0), b'20120410T100000'),
|
||||
(3, datetime(2012, 5, 1, 10, 0), b'20120501T100000'),
|
||||
(4, datetime(2012, 4, 17, 10, 0), b'20120417T100000')
|
||||
])
|
||||
def test_list_exdate_to_ical_is_inverse_of_from_ical(events, i, exception_date, exception_date_ics, in_timezone):
|
||||
exdate = events.event_with_recurrence_exdates_on_different_lines['exdate']
|
||||
assert exdate[i].dts[0].dt == in_timezone(exception_date, 'Europe/Vienna')
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("i", "exception_date", "exception_date_ics"),
|
||||
[
|
||||
(0, datetime(2012, 5, 29, 10, 0), b"20120529T100000"),
|
||||
(1, datetime(2012, 4, 3, 10, 0), b"20120403T100000"),
|
||||
(2, datetime(2012, 4, 10, 10, 0), b"20120410T100000"),
|
||||
(3, datetime(2012, 5, 1, 10, 0), b"20120501T100000"),
|
||||
(4, datetime(2012, 4, 17, 10, 0), b"20120417T100000"),
|
||||
],
|
||||
)
|
||||
def test_list_exdate_to_ical_is_inverse_of_from_ical(
|
||||
events, i, exception_date, exception_date_ics, in_timezone
|
||||
):
|
||||
exdate = events.event_with_recurrence_exdates_on_different_lines["exdate"]
|
||||
assert exdate[i].dts[0].dt == in_timezone(exception_date, "Europe/Vienna")
|
||||
assert exdate[i].to_ical() == exception_date_ics
|
||||
|
||||
@pytest.mark.parametrize('freq, byday, dtstart, expected', [
|
||||
# Test some YEARLY BYDAY repeats
|
||||
('YEARLY', '1SU', date(2016,1,3), # 1st Sunday in year
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 1SU\r\nDTSTART;VALUE=DATE:20160103\r\nRRULE:FREQ=YEARLY;BYDAY=1SU\r\nEND:VEVENT\r\n'),
|
||||
('YEARLY', '53MO', date(1984,12,31), # 53rd Monday in (leap) year
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 53MO\r\nDTSTART;VALUE=DATE:19841231\r\nRRULE:FREQ=YEARLY;BYDAY=53MO\r\nEND:VEVENT\r\n'),
|
||||
('YEARLY', '-1TU', date(1999,12,28), # Last Tuesday in year
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY -1TU\r\nDTSTART;VALUE=DATE:19991228\r\nRRULE:FREQ=YEARLY;BYDAY=-1TU\r\nEND:VEVENT\r\n'),
|
||||
('YEARLY', '-17WE', date(2000,9,6), # 17th-to-last Wednesday in year
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY -17WE\r\nDTSTART;VALUE=DATE:20000906\r\nRRULE:FREQ=YEARLY;BYDAY=-17WE\r\nEND:VEVENT\r\n'),
|
||||
# Test some MONTHLY BYDAY repeats
|
||||
('MONTHLY', '2TH', date(2003,4,10), # 2nd Thursday in month
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY 2TH\r\nDTSTART;VALUE=DATE:20030410\r\nRRULE:FREQ=MONTHLY;BYDAY=2TH\r\nEND:VEVENT\r\n'),
|
||||
('MONTHLY', '-3FR', date(2017,5,12), # 3rd-to-last Friday in month
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY -3FR\r\nDTSTART;VALUE=DATE:20170512\r\nRRULE:FREQ=MONTHLY;BYDAY=-3FR\r\nEND:VEVENT\r\n'),
|
||||
('MONTHLY', '-5SA', date(2053,11,1), # 5th-to-last Saturday in month
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY -5SA\r\nDTSTART;VALUE=DATE:20531101\r\nRRULE:FREQ=MONTHLY;BYDAY=-5SA\r\nEND:VEVENT\r\n'),
|
||||
# Specifically test examples from the report of Issue #518
|
||||
# https://github.com/collective/icalendar/issues/518
|
||||
('YEARLY', '9MO', date(2023,2,27), # 9th Monday in year
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 9MO\r\nDTSTART;VALUE=DATE:20230227\r\nRRULE:FREQ=YEARLY;BYDAY=9MO\r\nEND:VEVENT\r\n'),
|
||||
('YEARLY', '10MO', date(2023,3,6), # 10th Monday in year
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 10MO\r\nDTSTART;VALUE=DATE:20230306\r\nRRULE:FREQ=YEARLY;BYDAY=10MO\r\nEND:VEVENT\r\n'),
|
||||
])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("freq", "byday", "dtstart", "expected"),
|
||||
[
|
||||
# Test some YEARLY BYDAY repeats
|
||||
(
|
||||
"YEARLY",
|
||||
"1SU",
|
||||
date(2016, 1, 3), # 1st Sunday in year
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 1SU\r\nDTSTART;VALUE=DATE:20160103\r\nRRULE:FREQ=YEARLY;BYDAY=1SU\r\nEND:VEVENT\r\n",
|
||||
),
|
||||
(
|
||||
"YEARLY",
|
||||
"53MO",
|
||||
date(1984, 12, 31), # 53rd Monday in (leap) year
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 53MO\r\nDTSTART;VALUE=DATE:19841231\r\nRRULE:FREQ=YEARLY;BYDAY=53MO\r\nEND:VEVENT\r\n",
|
||||
),
|
||||
(
|
||||
"YEARLY",
|
||||
"-1TU",
|
||||
date(1999, 12, 28), # Last Tuesday in year
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY -1TU\r\nDTSTART;VALUE=DATE:19991228\r\nRRULE:FREQ=YEARLY;BYDAY=-1TU\r\nEND:VEVENT\r\n",
|
||||
),
|
||||
(
|
||||
"YEARLY",
|
||||
"-17WE",
|
||||
date(2000, 9, 6), # 17th-to-last Wednesday in year
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY -17WE\r\nDTSTART;VALUE=DATE:20000906\r\nRRULE:FREQ=YEARLY;BYDAY=-17WE\r\nEND:VEVENT\r\n",
|
||||
),
|
||||
# Test some MONTHLY BYDAY repeats
|
||||
(
|
||||
"MONTHLY",
|
||||
"2TH",
|
||||
date(2003, 4, 10), # 2nd Thursday in month
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY 2TH\r\nDTSTART;VALUE=DATE:20030410\r\nRRULE:FREQ=MONTHLY;BYDAY=2TH\r\nEND:VEVENT\r\n",
|
||||
),
|
||||
(
|
||||
"MONTHLY",
|
||||
"-3FR",
|
||||
date(2017, 5, 12), # 3rd-to-last Friday in month
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY -3FR\r\nDTSTART;VALUE=DATE:20170512\r\nRRULE:FREQ=MONTHLY;BYDAY=-3FR\r\nEND:VEVENT\r\n",
|
||||
),
|
||||
(
|
||||
"MONTHLY",
|
||||
"-5SA",
|
||||
date(2053, 11, 1), # 5th-to-last Saturday in month
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY -5SA\r\nDTSTART;VALUE=DATE:20531101\r\nRRULE:FREQ=MONTHLY;BYDAY=-5SA\r\nEND:VEVENT\r\n",
|
||||
),
|
||||
# Specifically test examples from the report of Issue #518
|
||||
# https://github.com/collective/icalendar/issues/518
|
||||
(
|
||||
"YEARLY",
|
||||
"9MO",
|
||||
date(2023, 2, 27), # 9th Monday in year
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 9MO\r\nDTSTART;VALUE=DATE:20230227\r\nRRULE:FREQ=YEARLY;BYDAY=9MO\r\nEND:VEVENT\r\n",
|
||||
),
|
||||
(
|
||||
"YEARLY",
|
||||
"10MO",
|
||||
date(2023, 3, 6), # 10th Monday in year
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 10MO\r\nDTSTART;VALUE=DATE:20230306\r\nRRULE:FREQ=YEARLY;BYDAY=10MO\r\nEND:VEVENT\r\n",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_byday_to_ical(freq, byday, dtstart, expected):
|
||||
'Test the BYDAY rule is correctly processed by to_ical().'
|
||||
"""Test the BYDAY rule is correctly processed by to_ical()."""
|
||||
event = Event()
|
||||
event.add('SUMMARY', ' '.join(['Event', freq, byday]))
|
||||
event.add('DTSTART', dtstart)
|
||||
event.add('RRULE', {'FREQ':[freq], 'BYDAY':byday})
|
||||
event.add("SUMMARY", " ".join(["Event", freq, byday]))
|
||||
event.add("DTSTART", dtstart)
|
||||
event.add("RRULE", {"FREQ": [freq], "BYDAY": byday})
|
||||
assert event.to_ical() == expected
|
||||
|
|
|
|||
|
|
@ -4,18 +4,20 @@ See
|
|||
- https://github.com/collective/icalendar/issues/653
|
||||
- https://www.rfc-editor.org/rfc/rfc7529.html
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from icalendar.prop import vRecur, vMonth, vSkip
|
||||
|
||||
from icalendar.prop import vMonth, vRecur, vSkip
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"uid,scale",
|
||||
("uid", "scale"),
|
||||
[
|
||||
("4.3.1", "CHINESE"),
|
||||
("4.3.2", "ETHIOPIC"),
|
||||
("4.3.3", "HEBREW"),
|
||||
("4.3.4", "GREGORIAN"),
|
||||
]
|
||||
],
|
||||
)
|
||||
def test_rscale(calendars, uid, scale):
|
||||
"""Check that the RSCALE is parsed correctly."""
|
||||
|
|
@ -27,13 +29,13 @@ def test_rscale(calendars, uid, scale):
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"uid,skip",
|
||||
("uid", "skip"),
|
||||
[
|
||||
("4.3.2", None),
|
||||
("4.3.3", ["FORWARD"]),
|
||||
]
|
||||
],
|
||||
)
|
||||
def test_rscale(calendars, uid, skip):
|
||||
def test_rscale_with_skip(calendars, uid, skip):
|
||||
"""Check that the RSCALE is parsed correctly."""
|
||||
event = calendars.rfc_7529.walk(select=lambda c: c.get("UID") == uid)[0]
|
||||
recur = event["RRULE"]
|
||||
|
|
@ -48,9 +50,13 @@ def test_leap_month(calendars):
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"ty, recur, ics",
|
||||
("ty", "recur", "ics"),
|
||||
[
|
||||
(vRecur, vRecur(rscale="CHINESE", freq="YEARLY"), b"RSCALE=CHINESE;FREQ=YEARLY"),
|
||||
(
|
||||
vRecur,
|
||||
vRecur(rscale="CHINESE", freq="YEARLY"),
|
||||
b"RSCALE=CHINESE;FREQ=YEARLY",
|
||||
),
|
||||
(vRecur, vRecur(bymonth=vMonth(10)), b"BYMONTH=10"),
|
||||
(vRecur, vRecur(bymonth=vMonth("5L")), b"BYMONTH=5L"),
|
||||
(vMonth, vMonth(10), b"10"),
|
||||
|
|
@ -61,9 +67,17 @@ def test_leap_month(calendars):
|
|||
(vSkip, vSkip("OMIT"), b"OMIT"),
|
||||
(vSkip, vSkip("BACKWARD"), b"BACKWARD"),
|
||||
(vSkip, vSkip("FORWARD"), b"FORWARD"),
|
||||
(vRecur, vRecur(rscale="GREGORIAN", freq="YEARLY", skip='FORWARD'), b"RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD"),
|
||||
(vRecur, vRecur(rscale="GREGORIAN", freq="YEARLY", skip=vSkip.FORWARD), b"RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD"),
|
||||
]
|
||||
(
|
||||
vRecur,
|
||||
vRecur(rscale="GREGORIAN", freq="YEARLY", skip="FORWARD"),
|
||||
b"RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD",
|
||||
),
|
||||
(
|
||||
vRecur,
|
||||
vRecur(rscale="GREGORIAN", freq="YEARLY", skip=vSkip.FORWARD),
|
||||
b"RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_conversion(ty, recur, ics):
|
||||
"""Test string conversion."""
|
||||
|
|
|
|||
|
|
@ -1,28 +1,30 @@
|
|||
import datetime
|
||||
import icalendar
|
||||
import os
|
||||
|
||||
import icalendar
|
||||
|
||||
|
||||
def test_value_type_is_not_mapped():
|
||||
"""Usually, the value should be absent."""
|
||||
assert 'X-SOMETIME' not in icalendar.cal.types_factory.types_map
|
||||
assert "X-SOMETIME" not in icalendar.cal.types_factory.types_map
|
||||
|
||||
|
||||
def test_value_type_is_mapped(x_sometime):
|
||||
"""The value is mapped for the test."""
|
||||
assert 'X-SOMETIME' in icalendar.cal.types_factory.types_map
|
||||
assert "X-SOMETIME" in icalendar.cal.types_factory.types_map
|
||||
|
||||
|
||||
def test_create_from_ical(x_sometime):
|
||||
directory = os.path.dirname(__file__)
|
||||
ics = open(os.path.join(directory, 'calendars', 'time.ics'), 'rb')
|
||||
ics = open(os.path.join(directory, "calendars", "time.ics"), "rb")
|
||||
cal = icalendar.Calendar.from_ical(ics.read())
|
||||
ics.close()
|
||||
|
||||
assert cal['X-SOMETIME'].dt == datetime.time(17, 20, 10)
|
||||
assert cal['X-SOMETIME'].to_ical() == '172010'
|
||||
assert cal["X-SOMETIME"].dt == datetime.time(17, 20, 10)
|
||||
assert cal["X-SOMETIME"].to_ical() == "172010"
|
||||
|
||||
|
||||
def test_create_to_ical(x_sometime):
|
||||
cal = icalendar.Calendar()
|
||||
cal.add('X-SOMETIME', datetime.time(17, 20, 10))
|
||||
assert b'X-SOMETIME;VALUE=TIME:172010' in cal.to_ical().splitlines()
|
||||
cal.add("X-SOMETIME", datetime.time(17, 20, 10))
|
||||
assert b"X-SOMETIME;VALUE=TIME:172010" in cal.to_ical().splitlines()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
import datetime
|
||||
|
||||
import dateutil.parser
|
||||
|
||||
import icalendar
|
||||
from icalendar.prop import tzid_from_dt
|
||||
|
||||
|
|
@ -9,49 +10,53 @@ def test_create_from_ical(calendars, other_tzp):
|
|||
"""Create a calendar from a .ics file."""
|
||||
cal = calendars.timezoned
|
||||
|
||||
assert cal['prodid'].to_ical() == b"-//Plone.org//NONSGML plone.app.event//EN"
|
||||
assert cal["prodid"].to_ical() == b"-//Plone.org//NONSGML plone.app.event//EN"
|
||||
|
||||
timezones = cal.walk('VTIMEZONE')
|
||||
timezones = cal.walk("VTIMEZONE")
|
||||
assert len(timezones) == 1
|
||||
|
||||
tz = timezones[0]
|
||||
assert tz['tzid'].to_ical() == b"Europe/Vienna"
|
||||
assert tz["tzid"].to_ical() == b"Europe/Vienna"
|
||||
|
||||
std = tz.walk('STANDARD')[0]
|
||||
assert std.decoded('TZOFFSETFROM') == datetime.timedelta(0, 7200)
|
||||
std = tz.walk("STANDARD")[0]
|
||||
assert std.decoded("TZOFFSETFROM") == datetime.timedelta(0, 7200)
|
||||
|
||||
ev1 = cal.walk('VEVENT')[0]
|
||||
assert ev1.decoded('DTSTART') == other_tzp.localize(datetime.datetime(2012, 2, 13, 10, 0, 0), 'Europe/Vienna')
|
||||
assert ev1.decoded('DTSTAMP') == other_tzp.localize(datetime.datetime(2010, 10, 10, 9, 10, 10), 'UTC')
|
||||
ev1 = cal.walk("VEVENT")[0]
|
||||
assert ev1.decoded("DTSTART") == other_tzp.localize(
|
||||
datetime.datetime(2012, 2, 13, 10, 0, 0), "Europe/Vienna"
|
||||
)
|
||||
assert ev1.decoded("DTSTAMP") == other_tzp.localize(
|
||||
datetime.datetime(2010, 10, 10, 9, 10, 10), "UTC"
|
||||
)
|
||||
|
||||
|
||||
def test_create_to_ical(tzp):
|
||||
cal = icalendar.Calendar()
|
||||
|
||||
cal.add('prodid', "-//Plone.org//NONSGML plone.app.event//EN")
|
||||
cal.add('version', "2.0")
|
||||
cal.add('x-wr-calname', "test create calendar")
|
||||
cal.add('x-wr-caldesc', "icalendar tests")
|
||||
cal.add('x-wr-relcalid', "12345")
|
||||
cal.add('x-wr-timezone', "Europe/Vienna")
|
||||
cal.add("prodid", "-//Plone.org//NONSGML plone.app.event//EN")
|
||||
cal.add("version", "2.0")
|
||||
cal.add("x-wr-calname", "test create calendar")
|
||||
cal.add("x-wr-caldesc", "icalendar tests")
|
||||
cal.add("x-wr-relcalid", "12345")
|
||||
cal.add("x-wr-timezone", "Europe/Vienna")
|
||||
|
||||
tzc = icalendar.Timezone()
|
||||
tzc.add('tzid', 'Europe/Vienna')
|
||||
tzc.add('x-lic-location', 'Europe/Vienna')
|
||||
tzc.add("tzid", "Europe/Vienna")
|
||||
tzc.add("x-lic-location", "Europe/Vienna")
|
||||
|
||||
tzs = icalendar.TimezoneStandard()
|
||||
tzs.add('tzname', 'CET')
|
||||
tzs.add('dtstart', datetime.datetime(1970, 10, 25, 3, 0, 0))
|
||||
tzs.add('rrule', {'freq': 'yearly', 'bymonth': 10, 'byday': '-1su'})
|
||||
tzs.add('TZOFFSETFROM', datetime.timedelta(hours=2))
|
||||
tzs.add('TZOFFSETTO', datetime.timedelta(hours=1))
|
||||
tzs.add("tzname", "CET")
|
||||
tzs.add("dtstart", datetime.datetime(1970, 10, 25, 3, 0, 0))
|
||||
tzs.add("rrule", {"freq": "yearly", "bymonth": 10, "byday": "-1su"})
|
||||
tzs.add("TZOFFSETFROM", datetime.timedelta(hours=2))
|
||||
tzs.add("TZOFFSETTO", datetime.timedelta(hours=1))
|
||||
|
||||
tzd = icalendar.TimezoneDaylight()
|
||||
tzd.add('tzname', 'CEST')
|
||||
tzd.add('dtstart', datetime.datetime(1970, 3, 29, 2, 0, 0))
|
||||
tzs.add('rrule', {'freq': 'yearly', 'bymonth': 3, 'byday': '-1su'})
|
||||
tzd.add('TZOFFSETFROM', datetime.timedelta(hours=1))
|
||||
tzd.add('TZOFFSETTO', datetime.timedelta(hours=2))
|
||||
tzd.add("tzname", "CEST")
|
||||
tzd.add("dtstart", datetime.datetime(1970, 3, 29, 2, 0, 0))
|
||||
tzs.add("rrule", {"freq": "yearly", "bymonth": 3, "byday": "-1su"})
|
||||
tzd.add("TZOFFSETFROM", datetime.timedelta(hours=1))
|
||||
tzd.add("TZOFFSETTO", datetime.timedelta(hours=2))
|
||||
|
||||
tzc.add_component(tzs)
|
||||
tzc.add_component(tzd)
|
||||
|
|
@ -59,36 +64,41 @@ def test_create_to_ical(tzp):
|
|||
|
||||
event = icalendar.Event()
|
||||
event.add(
|
||||
'dtstart',
|
||||
tzp.localize(datetime.datetime(2012, 2, 13, 10, 00, 00), "Europe/Vienna"))
|
||||
"dtstart",
|
||||
tzp.localize(datetime.datetime(2012, 2, 13, 10, 00, 00), "Europe/Vienna"),
|
||||
)
|
||||
event.add(
|
||||
'dtend',
|
||||
tzp.localize(datetime.datetime(2012, 2, 17, 18, 00, 00), "Europe/Vienna"))
|
||||
"dtend",
|
||||
tzp.localize(datetime.datetime(2012, 2, 17, 18, 00, 00), "Europe/Vienna"),
|
||||
)
|
||||
event.add(
|
||||
'dtstamp',
|
||||
tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna"))
|
||||
"dtstamp",
|
||||
tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna"),
|
||||
)
|
||||
event.add(
|
||||
'created',
|
||||
tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna"))
|
||||
event.add('uid', '123456')
|
||||
"created",
|
||||
tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna"),
|
||||
)
|
||||
event.add("uid", "123456")
|
||||
event.add(
|
||||
'last-modified',
|
||||
tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna"))
|
||||
event.add('summary', 'artsprint 2012')
|
||||
"last-modified",
|
||||
tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna"),
|
||||
)
|
||||
event.add("summary", "artsprint 2012")
|
||||
# event.add('rrule', 'FREQ=YEARLY;INTERVAL=1;COUNT=10')
|
||||
event.add('description', 'sprinting at the artsprint')
|
||||
event.add('location', 'aka bild, wien')
|
||||
event.add('categories', 'first subject')
|
||||
event.add('categories', 'second subject')
|
||||
event.add('attendee', 'häns')
|
||||
event.add('attendee', 'franz')
|
||||
event.add('attendee', 'sepp')
|
||||
event.add('contact', 'Max Mustermann, 1010 Wien')
|
||||
event.add('url', 'https://plone.org')
|
||||
event.add("description", "sprinting at the artsprint")
|
||||
event.add("location", "aka bild, wien")
|
||||
event.add("categories", "first subject")
|
||||
event.add("categories", "second subject")
|
||||
event.add("attendee", "häns")
|
||||
event.add("attendee", "franz")
|
||||
event.add("attendee", "sepp")
|
||||
event.add("contact", "Max Mustermann, 1010 Wien")
|
||||
event.add("url", "https://plone.org")
|
||||
cal.add_component(event)
|
||||
|
||||
test_out = b'|'.join(cal.to_ical().splitlines())
|
||||
test_out = test_out.decode('utf-8')
|
||||
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:19701025T03"
|
||||
|
|
@ -100,61 +110,67 @@ def test_create_to_ical(tzp):
|
|||
assert vtimezone_lines in test_out
|
||||
|
||||
test_str = "DTSTART;TZID=Europe/Vienna:20120213T100000"
|
||||
assert (test_str in test_out)
|
||||
assert ("ATTENDEE:sepp" in test_out)
|
||||
assert test_str in test_out
|
||||
assert "ATTENDEE:sepp" in test_out
|
||||
|
||||
# ical standard expects DTSTAMP and CREATED in UTC
|
||||
assert ("DTSTAMP:20101010T081010Z" in test_out)
|
||||
assert ("CREATED:20101010T081010Z" in test_out)
|
||||
assert "DTSTAMP:20101010T081010Z" in test_out
|
||||
assert "CREATED:20101010T081010Z" in test_out
|
||||
|
||||
|
||||
def test_tzinfo_dateutil():
|
||||
"""Test for issues #77, #63
|
||||
references: #73,7430b66862346fe3a6a100ab25e35a8711446717
|
||||
"""
|
||||
date = dateutil.parser.parse('2012-08-30T22:41:00Z')
|
||||
date2 = dateutil.parser.parse('2012-08-30T22:41:00 +02:00')
|
||||
assert (date.tzinfo.__module__.startswith('dateutil.tz'))
|
||||
assert (date2.tzinfo.__module__.startswith('dateutil.tz'))
|
||||
date = dateutil.parser.parse("2012-08-30T22:41:00Z")
|
||||
date2 = dateutil.parser.parse("2012-08-30T22:41:00 +02:00")
|
||||
assert date.tzinfo.__module__.startswith("dateutil.tz")
|
||||
assert date2.tzinfo.__module__.startswith("dateutil.tz")
|
||||
|
||||
# make sure, it's parsed properly and doesn't throw an error
|
||||
assert (icalendar.vDDDTypes(date).to_ical()
|
||||
== b'20120830T224100Z')
|
||||
assert (icalendar.vDDDTypes(date2).to_ical()
|
||||
== b'20120830T224100')
|
||||
assert icalendar.vDDDTypes(date).to_ical() == b"20120830T224100Z"
|
||||
assert icalendar.vDDDTypes(date2).to_ical() == b"20120830T224100"
|
||||
|
||||
|
||||
def test_create_america_new_york(calendars, tzp):
|
||||
"""testing America/New_York, the most complex example from the RFC"""
|
||||
cal = calendars.america_new_york
|
||||
dt = cal.walk('VEVENT')[0]['DTSTART'][0].dt
|
||||
assert tzid_from_dt(dt) in ('custom_America/New_York', "EDT")
|
||||
dt = cal.walk("VEVENT")[0]["DTSTART"][0].dt
|
||||
assert tzid_from_dt(dt) in ("custom_America/New_York", "EDT")
|
||||
|
||||
|
||||
def test_america_new_york_with_pytz(calendars, tzp, pytz_only):
|
||||
"""Create a custom timezone with pytz and test the transition times."""
|
||||
print(tzp)
|
||||
cal = calendars.america_new_york
|
||||
dt = cal.walk('VEVENT')[0]['DTSTART'][0].dt
|
||||
dt = cal.walk("VEVENT")[0]["DTSTART"][0].dt
|
||||
tz = dt.tzinfo
|
||||
tz_new_york = tzp.timezone('America/New_York')
|
||||
tz_new_york = tzp.timezone("America/New_York")
|
||||
# for reasons (tm) the locally installed version of the timezone
|
||||
# database isn't always complete, therefore we only compare some
|
||||
# transition times
|
||||
ny_transition_times = []
|
||||
ny_transition_info = []
|
||||
for num, date in enumerate(tz_new_york._utc_transition_times):
|
||||
if datetime.datetime(1967, 4, 30, 7, 0)\
|
||||
<= date <= datetime.datetime(2037, 11, 1, 6, 0):
|
||||
if (
|
||||
datetime.datetime(1967, 4, 30, 7, 0)
|
||||
<= date
|
||||
<= datetime.datetime(2037, 11, 1, 6, 0)
|
||||
):
|
||||
ny_transition_times.append(date)
|
||||
ny_transition_info.append(tz_new_york._transition_info[num])
|
||||
assert tz._utc_transition_times[:142] == ny_transition_times
|
||||
assert tz._transition_info[0:142] == ny_transition_info
|
||||
assert (
|
||||
datetime.timedelta(-1, 72000),
|
||||
datetime.timedelta(0, 3600), 'EDT'
|
||||
) in tz._tzinfos.keys()
|
||||
assert (datetime.timedelta(-1, 68400), datetime.timedelta(0), 'EST') in tz._tzinfos.keys()
|
||||
datetime.timedelta(-1, 72000),
|
||||
datetime.timedelta(0, 3600),
|
||||
"EDT",
|
||||
) in tz._tzinfos.keys()
|
||||
assert (
|
||||
datetime.timedelta(-1, 68400),
|
||||
datetime.timedelta(0),
|
||||
"EST",
|
||||
) in tz._tzinfos.keys()
|
||||
|
||||
|
||||
fiji_transition_times = [
|
||||
|
|
@ -221,68 +237,85 @@ fiji_transition_times = [
|
|||
datetime.datetime(2037, 1, 17, 13, 0),
|
||||
datetime.datetime(2037, 10, 24, 14, 0),
|
||||
datetime.datetime(2038, 1, 23, 13, 0),
|
||||
datetime.datetime(2038, 10, 23, 14, 0)
|
||||
datetime.datetime(2038, 10, 23, 14, 0),
|
||||
]
|
||||
|
||||
fiji_transition_info = (
|
||||
[(
|
||||
datetime.timedelta(0, 43200),
|
||||
datetime.timedelta(0),
|
||||
'custom_Pacific/Fiji_19151026T000000_+115544_+1200'
|
||||
)] +
|
||||
3 * [(
|
||||
datetime.timedelta(0, 46800),
|
||||
datetime.timedelta(0, 3600),
|
||||
'custom_Pacific/Fiji_19981101T020000_+1200_+1300'
|
||||
), (
|
||||
datetime.timedelta(0, 43200),
|
||||
datetime.timedelta(0),
|
||||
'custom_Pacific/Fiji_19990228T030000_+1300_+1200')
|
||||
] +
|
||||
3 * [(
|
||||
datetime.timedelta(0, 46800),
|
||||
datetime.timedelta(0, 3600),
|
||||
'custom_Pacific/Fiji_20101024T020000_+1200_+1300'
|
||||
), (
|
||||
datetime.timedelta(0, 43200),
|
||||
datetime.timedelta(0),
|
||||
'custom_Pacific/Fiji_19990228T030000_+1300_+1200'
|
||||
)] +
|
||||
25 * [(
|
||||
datetime.timedelta(0, 46800),
|
||||
datetime.timedelta(0, 3600),
|
||||
'custom_Pacific/Fiji_20101024T020000_+1200_+1300'
|
||||
), (
|
||||
datetime.timedelta(0, 43200),
|
||||
datetime.timedelta(0),
|
||||
'custom_Pacific/Fiji_20140119T020000_+1300_+1200'
|
||||
)] +
|
||||
[(
|
||||
datetime.timedelta(0, 46800),
|
||||
datetime.timedelta(0, 3600),
|
||||
'custom_Pacific/Fiji_20101024T020000_+1200_+1300'
|
||||
)]
|
||||
[
|
||||
(
|
||||
datetime.timedelta(0, 43200),
|
||||
datetime.timedelta(0),
|
||||
"custom_Pacific/Fiji_19151026T000000_+115544_+1200",
|
||||
)
|
||||
]
|
||||
+ 3
|
||||
* [
|
||||
(
|
||||
datetime.timedelta(0, 46800),
|
||||
datetime.timedelta(0, 3600),
|
||||
"custom_Pacific/Fiji_19981101T020000_+1200_+1300",
|
||||
),
|
||||
(
|
||||
datetime.timedelta(0, 43200),
|
||||
datetime.timedelta(0),
|
||||
"custom_Pacific/Fiji_19990228T030000_+1300_+1200",
|
||||
),
|
||||
]
|
||||
+ 3
|
||||
* [
|
||||
(
|
||||
datetime.timedelta(0, 46800),
|
||||
datetime.timedelta(0, 3600),
|
||||
"custom_Pacific/Fiji_20101024T020000_+1200_+1300",
|
||||
),
|
||||
(
|
||||
datetime.timedelta(0, 43200),
|
||||
datetime.timedelta(0),
|
||||
"custom_Pacific/Fiji_19990228T030000_+1300_+1200",
|
||||
),
|
||||
]
|
||||
+ 25
|
||||
* [
|
||||
(
|
||||
datetime.timedelta(0, 46800),
|
||||
datetime.timedelta(0, 3600),
|
||||
"custom_Pacific/Fiji_20101024T020000_+1200_+1300",
|
||||
),
|
||||
(
|
||||
datetime.timedelta(0, 43200),
|
||||
datetime.timedelta(0),
|
||||
"custom_Pacific/Fiji_20140119T020000_+1300_+1200",
|
||||
),
|
||||
]
|
||||
+ [
|
||||
(
|
||||
datetime.timedelta(0, 46800),
|
||||
datetime.timedelta(0, 3600),
|
||||
"custom_Pacific/Fiji_20101024T020000_+1200_+1300",
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_create_pacific_fiji(calendars, pytz_only):
|
||||
"""testing Pacific/Fiji, another pretty complex example with more than
|
||||
one RDATE property per subcomponent"""
|
||||
cal = calendars.pacific_fiji
|
||||
|
||||
tz = cal.walk('VEVENT')[0]['DTSTART'][0].dt.tzinfo
|
||||
assert str(tz) == 'custom_Pacific/Fiji'
|
||||
tz = cal.walk("VEVENT")[0]["DTSTART"][0].dt.tzinfo
|
||||
assert str(tz) == "custom_Pacific/Fiji"
|
||||
assert tz._utc_transition_times == fiji_transition_times
|
||||
assert tz._transition_info == fiji_transition_info
|
||||
assert (
|
||||
datetime.timedelta(0, 46800),
|
||||
datetime.timedelta(0, 3600),
|
||||
'custom_Pacific/Fiji_19981101T020000_+1200_+1300'
|
||||
) in tz._tzinfos.keys()
|
||||
datetime.timedelta(0, 46800),
|
||||
datetime.timedelta(0, 3600),
|
||||
"custom_Pacific/Fiji_19981101T020000_+1200_+1300",
|
||||
) in tz._tzinfos.keys()
|
||||
assert (
|
||||
datetime.timedelta(0, 43200),
|
||||
datetime.timedelta(0),
|
||||
'custom_Pacific/Fiji_19990228T030000_+1300_+1200'
|
||||
) in tz._tzinfos.keys()
|
||||
datetime.timedelta(0, 43200),
|
||||
datetime.timedelta(0),
|
||||
"custom_Pacific/Fiji_19990228T030000_+1300_+1200",
|
||||
) in tz._tzinfos.keys()
|
||||
|
||||
|
||||
# these are the expected offsets before and after the fiji_transition_times
|
||||
|
|
@ -357,7 +390,7 @@ fiji_expected_offsets = [
|
|||
def test_transition_times_fiji(tzp, timezones):
|
||||
"""The transition times are computed."""
|
||||
tz = timezones.pacific_fiji.to_tz(tzp)
|
||||
offsets = [] # [(before, after), ...]
|
||||
offsets = [] # [(before, after), ...]
|
||||
for i, transition_time in enumerate(fiji_transition_times):
|
||||
before_after_offset = []
|
||||
for offset in (datetime.timedelta(hours=-1), datetime.timedelta(hours=+1)):
|
||||
|
|
@ -372,42 +405,43 @@ def test_same_start_date(calendars):
|
|||
"""testing if we can handle VTIMEZONEs whose different components
|
||||
have the same start DTIMEs."""
|
||||
cal = calendars.timezone_same_start
|
||||
d = cal.subcomponents[1]['DTSTART'].dt
|
||||
assert d.strftime('%c') == 'Fri Feb 24 12:00:00 2017'
|
||||
d = cal.subcomponents[1]["DTSTART"].dt
|
||||
assert d.strftime("%c") == "Fri Feb 24 12:00:00 2017"
|
||||
|
||||
|
||||
def test_same_start_date_and_offset(calendars):
|
||||
"""testing if we can handle VTIMEZONEs whose different components
|
||||
have the same DTSTARTs, TZOFFSETFROM, and TZOFFSETTO."""
|
||||
cal = calendars.timezone_same_start_and_offset
|
||||
d = cal.subcomponents[1]['DTSTART'].dt
|
||||
assert d.strftime('%c') == 'Fri Feb 24 12:00:00 2017'
|
||||
d = cal.subcomponents[1]["DTSTART"].dt
|
||||
assert d.strftime("%c") == "Fri Feb 24 12:00:00 2017"
|
||||
|
||||
|
||||
def test_rdate(calendars):
|
||||
"""testing if we can handle VTIMEZONEs who only have an RDATE, not RRULE
|
||||
"""
|
||||
"""testing if we can handle VTIMEZONEs who only have an RDATE, not RRULE"""
|
||||
cal = calendars.timezone_rdate
|
||||
vevent = cal.walk('VEVENT')[0]
|
||||
assert tzid_from_dt(vevent['DTSTART'].dt) in ('posix/Europe/Vaduz', "CET")
|
||||
vevent = cal.walk("VEVENT")[0]
|
||||
assert tzid_from_dt(vevent["DTSTART"].dt) in ("posix/Europe/Vaduz", "CET")
|
||||
|
||||
|
||||
def test_rdate_pytz(calendars, pytz_only):
|
||||
"""testing if we can handle VTIMEZONEs who only have an RDATE, not RRULE
|
||||
"""
|
||||
"""testing if we can handle VTIMEZONEs who only have an RDATE, not RRULE"""
|
||||
cal = calendars.timezone_rdate
|
||||
vevent = cal.walk('VEVENT')[0]
|
||||
tz = vevent['DTSTART'].dt.tzinfo
|
||||
vevent = cal.walk("VEVENT")[0]
|
||||
tz = vevent["DTSTART"].dt.tzinfo
|
||||
assert tz._utc_transition_times[:6] == [
|
||||
datetime.datetime(1901, 12, 13, 20, 45, 38),
|
||||
datetime.datetime(1941, 5, 5, 0, 0, 0),
|
||||
datetime.datetime(1941, 10, 6, 0, 0, 0),
|
||||
datetime.datetime(1942, 5, 4, 0, 0, 0),
|
||||
datetime.datetime(1942, 10, 5, 0, 0, 0),
|
||||
datetime.datetime(1981, 3, 29, 1, 0),
|
||||
]
|
||||
datetime.datetime(1901, 12, 13, 20, 45, 38),
|
||||
datetime.datetime(1941, 5, 5, 0, 0, 0),
|
||||
datetime.datetime(1941, 10, 6, 0, 0, 0),
|
||||
datetime.datetime(1942, 5, 4, 0, 0, 0),
|
||||
datetime.datetime(1942, 10, 5, 0, 0, 0),
|
||||
datetime.datetime(1981, 3, 29, 1, 0),
|
||||
]
|
||||
assert tz._transition_info[:6] == [
|
||||
(datetime.timedelta(0, 3600), datetime.timedelta(0), 'CET'),
|
||||
(datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), 'CEST'),
|
||||
(datetime.timedelta(0, 3600), datetime.timedelta(0), 'CET'),
|
||||
(datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), 'CEST'),
|
||||
(datetime.timedelta(0, 3600), datetime.timedelta(0), 'CET'),
|
||||
(datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), 'CEST'),
|
||||
]
|
||||
(datetime.timedelta(0, 3600), datetime.timedelta(0), "CET"),
|
||||
(datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), "CEST"),
|
||||
(datetime.timedelta(0, 3600), datetime.timedelta(0), "CET"),
|
||||
(datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), "CEST"),
|
||||
(datetime.timedelta(0, 3600), datetime.timedelta(0), "CET"),
|
||||
(datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), "CEST"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import itertools
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
import icalendar
|
||||
import re
|
||||
from icalendar.cal import Component, Calendar, Event
|
||||
from icalendar import prop
|
||||
from icalendar.cal import Calendar, Component, Event
|
||||
from icalendar.prop import tzid_from_dt
|
||||
|
||||
|
||||
|
|
@ -21,15 +20,15 @@ def test_nonempty_calendar_component(calendar_component):
|
|||
"""Every key defines a property.A property can consist of either a
|
||||
single item. This can be set with a single value...
|
||||
"""
|
||||
calendar_component['prodid'] = '-//max m//icalendar.mxm.dk/'
|
||||
calendar_component["prodid"] = "-//max m//icalendar.mxm.dk/"
|
||||
assert not calendar_component.is_empty()
|
||||
assert calendar_component == Calendar({'PRODID': '-//max m//icalendar.mxm.dk/'})
|
||||
assert calendar_component == Calendar({"PRODID": "-//max m//icalendar.mxm.dk/"})
|
||||
|
||||
# or with a list
|
||||
calendar_component['ATTENDEE'] = ['Max M', 'Rasmussen']
|
||||
calendar_component["ATTENDEE"] = ["Max M", "Rasmussen"]
|
||||
assert calendar_component == Calendar(
|
||||
{'ATTENDEE': ['Max M', 'Rasmussen'],
|
||||
'PRODID': '-//max m//icalendar.mxm.dk/'})
|
||||
{"ATTENDEE": ["Max M", "Rasmussen"], "PRODID": "-//max m//icalendar.mxm.dk/"}
|
||||
)
|
||||
|
||||
|
||||
def test_add_multiple_values(event_component):
|
||||
|
|
@ -39,57 +38,62 @@ def test_add_multiple_values(event_component):
|
|||
a list or not.
|
||||
"""
|
||||
# add multiple values at once
|
||||
event_component.add('attendee',
|
||||
['test@test.com', 'test2@test.com'])
|
||||
event_component.add("attendee", ["test@test.com", "test2@test.com"])
|
||||
|
||||
# or add one per line
|
||||
event_component.add('attendee', 'maxm@mxm.dk')
|
||||
event_component.add('attendee', 'test@example.dk')
|
||||
event_component.add("attendee", "maxm@mxm.dk")
|
||||
event_component.add("attendee", "test@example.dk")
|
||||
|
||||
# add again multiple values at once to very concatenaton of lists
|
||||
event_component.add('attendee',
|
||||
['test3@test.com', 'test4@test.com'])
|
||||
event_component.add("attendee", ["test3@test.com", "test4@test.com"])
|
||||
|
||||
assert event_component == Event({'ATTENDEE': [
|
||||
prop.vCalAddress('test@test.com'),
|
||||
prop.vCalAddress('test2@test.com'),
|
||||
prop.vCalAddress('maxm@mxm.dk'),
|
||||
prop.vCalAddress('test@example.dk'),
|
||||
prop.vCalAddress('test3@test.com'),
|
||||
prop.vCalAddress('test4@test.com')
|
||||
]})
|
||||
assert event_component == Event(
|
||||
{
|
||||
"ATTENDEE": [
|
||||
prop.vCalAddress("test@test.com"),
|
||||
prop.vCalAddress("test2@test.com"),
|
||||
prop.vCalAddress("maxm@mxm.dk"),
|
||||
prop.vCalAddress("test@example.dk"),
|
||||
prop.vCalAddress("test3@test.com"),
|
||||
prop.vCalAddress("test4@test.com"),
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_get_content_directly(c):
|
||||
"""You can get the values back directly ..."""
|
||||
c.add('prodid', '-//my product//')
|
||||
assert c['prodid'] == prop.vText('-//my product//')
|
||||
c.add("prodid", "-//my product//")
|
||||
assert c["prodid"] == prop.vText("-//my product//")
|
||||
# ... or decoded to a python type
|
||||
assert c.decoded('prodid') == b'-//my product//'
|
||||
assert c.decoded("prodid") == b"-//my product//"
|
||||
|
||||
|
||||
def test_get_default_value(c):
|
||||
"""With default values for non existing properties"""
|
||||
assert c.decoded('version', 'No Version') == 'No Version'
|
||||
assert c.decoded("version", "No Version") == "No Version"
|
||||
|
||||
|
||||
def test_default_list_example(c):
|
||||
c.add('rdate', [datetime(2013, 3, 28), datetime(2013, 3, 27)])
|
||||
assert isinstance(c.decoded('rdate'), prop.vDDDLists)
|
||||
c.add("rdate", [datetime(2013, 3, 28), datetime(2013, 3, 27)])
|
||||
assert isinstance(c.decoded("rdate"), prop.vDDDLists)
|
||||
|
||||
|
||||
def test_render_component(calendar_component):
|
||||
"""The component can render itself in the RFC 5545 format."""
|
||||
calendar_component.add('attendee', 'Max M')
|
||||
assert calendar_component.to_ical() == b'BEGIN:VCALENDAR\r\nATTENDEE:Max M\r\nEND:VCALENDAR\r\n'
|
||||
calendar_component.add("attendee", "Max M")
|
||||
assert (
|
||||
calendar_component.to_ical()
|
||||
== b"BEGIN:VCALENDAR\r\nATTENDEE:Max M\r\nEND:VCALENDAR\r\n"
|
||||
)
|
||||
|
||||
|
||||
def test_nested_component_event_ics(filled_event_component):
|
||||
"""Check the ical string of the event component."""
|
||||
assert filled_event_component.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"BEGIN:VEVENT\r\nDTEND:20000102T000000\r\n"
|
||||
+ b"DTSTART:20000101T000000\r\nSUMMARY:A brief history of time\r"
|
||||
+ b"\nEND:VEVENT\r\n"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -98,58 +102,75 @@ def test_nested_components(calendar_component, filled_event_component):
|
|||
holds events."""
|
||||
self.assertEqual(
|
||||
calendar_component.subcomponents,
|
||||
[Event({'DTEND': '20000102T000000', 'DTSTART': '20000101T000000',
|
||||
'SUMMARY': 'A brief history of time'})]
|
||||
[
|
||||
Event(
|
||||
{
|
||||
"DTEND": "20000102T000000",
|
||||
"DTSTART": "20000101T000000",
|
||||
"SUMMARY": "A brief history of time",
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def test_walk_filled_calendar_component(calendar_component, filled_event_component):
|
||||
"""We can walk over nested componentes with the walk method."""
|
||||
assert [i.name for i in calendar_component.walk()] == ['VCALENDAR', 'VEVENT']
|
||||
assert [i.name for i in calendar_component.walk()] == ["VCALENDAR", "VEVENT"]
|
||||
|
||||
|
||||
def test_filter_walk(calendar_component, filled_event_component):
|
||||
"""We can also just walk over specific component types, by filtering
|
||||
them on their name."""
|
||||
assert [i.name for i in calendar_component.walk('VEVENT')] == ['VEVENT']
|
||||
assert [i['dtstart'] for i in calendar_component.walk('VEVENT')] == ['20000101T000000']
|
||||
assert [i.name for i in calendar_component.walk("VEVENT")] == ["VEVENT"]
|
||||
assert [i["dtstart"] for i in calendar_component.walk("VEVENT")] == [
|
||||
"20000101T000000"
|
||||
]
|
||||
|
||||
|
||||
def test_recursive_property_items(calendar_component, filled_event_component):
|
||||
"""We can enumerate property items recursively with the property_items
|
||||
method."""
|
||||
calendar_component.add('attendee', 'Max M')
|
||||
calendar_component.add("attendee", "Max M")
|
||||
assert calendar_component.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')]
|
||||
("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"),
|
||||
]
|
||||
|
||||
|
||||
def test_flat_property_items(calendar_component, filled_event_component):
|
||||
"""We can also enumerate property items just under the component."""
|
||||
assert calendar_component.property_items(recursive=False) == [
|
||||
('BEGIN', b'VCALENDAR'),
|
||||
('ATTENDEE', prop.vCalAddress('Max M')),
|
||||
('END', b'VCALENDAR')]
|
||||
("BEGIN", b"VCALENDAR"),
|
||||
("ATTENDEE", prop.vCalAddress("Max M")),
|
||||
("END", b"VCALENDAR"),
|
||||
]
|
||||
|
||||
|
||||
def test_flat_property_items(filled_event_component):
|
||||
"""Flat enumeration on the event."""
|
||||
assert filled_event_component.property_items(recursive=False) == [
|
||||
('BEGIN', b'VEVENT'), ('DTEND', '20000102T000000'),
|
||||
('DTSTART', '20000101T000000'),
|
||||
('SUMMARY', 'A brief history of time'), ('END', b'VEVENT')]
|
||||
("BEGIN", b"VEVENT"),
|
||||
("DTEND", "20000102T000000"),
|
||||
("DTSTART", "20000101T000000"),
|
||||
("SUMMARY", "A brief history of time"),
|
||||
("END", b"VEVENT"),
|
||||
]
|
||||
|
||||
|
||||
def test_indent():
|
||||
"""Text fields which span multiple mulitple lines require proper indenting"""
|
||||
c = Calendar()
|
||||
c['description'] = 'Paragraph one\n\nParagraph two'
|
||||
c["description"] = "Paragraph one\n\nParagraph two"
|
||||
assert c.to_ical() == (
|
||||
b'BEGIN:VCALENDAR\r\nDESCRIPTION:Paragraph one\\n\\nParagraph two'
|
||||
+ b'\r\nEND:VCALENDAR\r\n'
|
||||
b"BEGIN:VCALENDAR\r\nDESCRIPTION:Paragraph one\\n\\nParagraph two"
|
||||
+ b"\r\nEND:VCALENDAR\r\n"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -157,10 +178,12 @@ def test_INLINE_properties(calendar_with_resources):
|
|||
"""INLINE properties have their values on one property line. Note the
|
||||
double quoting of the value with a colon in it.
|
||||
"""
|
||||
assert calendar_with_resources == Calendar({'RESOURCES': 'Chair, Table, "Room: 42"'})
|
||||
assert calendar_with_resources == Calendar(
|
||||
{"RESOURCES": 'Chair, Table, "Room: 42"'}
|
||||
)
|
||||
assert calendar_with_resources.to_ical() == (
|
||||
b'BEGIN:VCALENDAR\r\nRESOURCES:Chair\\, Table\\, "Room: 42"\r\n'
|
||||
+ b'END:VCALENDAR\r\n'
|
||||
+ b"END:VCALENDAR\r\n"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -168,32 +191,48 @@ def test_get_inline(calendar_with_resources):
|
|||
"""The inline values must be handled by the get_inline() and
|
||||
set_inline() methods.
|
||||
"""
|
||||
assert calendar_with_resources.get_inline('resources', decode=0) == \
|
||||
['Chair', 'Table', 'Room: 42']
|
||||
assert calendar_with_resources.get_inline("resources", decode=0) == [
|
||||
"Chair",
|
||||
"Table",
|
||||
"Room: 42",
|
||||
]
|
||||
|
||||
|
||||
def test_get_inline_decoded(calendar_with_resources):
|
||||
"""These can also be decoded"""
|
||||
assert calendar_with_resources.get_inline('resources', decode=1) == \
|
||||
[b'Chair', b'Table', b'Room: 42']
|
||||
assert calendar_with_resources.get_inline("resources", decode=1) == [
|
||||
b"Chair",
|
||||
b"Table",
|
||||
b"Room: 42",
|
||||
]
|
||||
|
||||
|
||||
def test_set_inline(calendar_with_resources):
|
||||
"""You can set them directly ..."""
|
||||
calendar_with_resources.set_inline('resources', ['A', 'List', 'of', 'some, recources'],
|
||||
encode=1)
|
||||
assert calendar_with_resources['resources'] == 'A,List,of,"some, recources"'
|
||||
assert calendar_with_resources.get_inline('resources', decode=0) == ['A', 'List', 'of', 'some, recources']
|
||||
calendar_with_resources.set_inline(
|
||||
"resources", ["A", "List", "of", "some, recources"], encode=1
|
||||
)
|
||||
assert calendar_with_resources["resources"] == 'A,List,of,"some, recources"'
|
||||
assert calendar_with_resources.get_inline("resources", decode=0) == [
|
||||
"A",
|
||||
"List",
|
||||
"of",
|
||||
"some, recources",
|
||||
]
|
||||
|
||||
|
||||
def test_inline_free_busy_inline(c):
|
||||
c['freebusy'] = '19970308T160000Z/PT3H,19970308T200000Z/PT1H,'\
|
||||
+ '19970308T230000Z/19970309T000000Z'
|
||||
assert c.get_inline('freebusy', decode=0) == \
|
||||
['19970308T160000Z/PT3H', '19970308T200000Z/PT1H',
|
||||
'19970308T230000Z/19970309T000000Z']
|
||||
c["freebusy"] = (
|
||||
"19970308T160000Z/PT3H,19970308T200000Z/PT1H,"
|
||||
+ "19970308T230000Z/19970309T000000Z"
|
||||
)
|
||||
assert c.get_inline("freebusy", decode=0) == [
|
||||
"19970308T160000Z/PT3H",
|
||||
"19970308T200000Z/PT1H",
|
||||
"19970308T230000Z/19970309T000000Z",
|
||||
]
|
||||
|
||||
freebusy = c.get_inline('freebusy', decode=1)
|
||||
freebusy = c.get_inline("freebusy", decode=1)
|
||||
assert isinstance(freebusy[0][0], datetime)
|
||||
assert isinstance(freebusy[0][1], timedelta)
|
||||
|
||||
|
|
@ -202,11 +241,10 @@ def test_cal_Component_add(comp, tzp):
|
|||
"""Test the for timezone correctness: dtstart should preserve it's
|
||||
timezone, created, dtstamp and last-modified must be in UTC.
|
||||
"""
|
||||
comp.add('dtstart', tzp.localize(datetime(2010, 10, 10, 10, 0, 0), "Europe/Vienna"))
|
||||
comp.add('created', datetime(2010, 10, 10, 12, 0, 0))
|
||||
comp.add('dtstamp', tzp.localize(datetime(2010, 10, 10, 14, 0, 0), "Europe/Vienna"))
|
||||
comp.add('last-modified', tzp.localize_utc(
|
||||
datetime(2010, 10, 10, 16, 0, 0)))
|
||||
comp.add("dtstart", tzp.localize(datetime(2010, 10, 10, 10, 0, 0), "Europe/Vienna"))
|
||||
comp.add("created", datetime(2010, 10, 10, 12, 0, 0))
|
||||
comp.add("dtstamp", tzp.localize(datetime(2010, 10, 10, 14, 0, 0), "Europe/Vienna"))
|
||||
comp.add("last-modified", tzp.localize_utc(datetime(2010, 10, 10, 16, 0, 0)))
|
||||
|
||||
lines = comp.to_ical().splitlines()
|
||||
assert b"DTSTART;TZID=Europe/Vienna:20101010T100000" in lines
|
||||
|
|
@ -216,22 +254,20 @@ def test_cal_Component_add(comp, tzp):
|
|||
|
||||
|
||||
def test_cal_Component_add_no_reencode(comp):
|
||||
"""Already encoded values should not be re-encoded.
|
||||
"""
|
||||
comp.add('ATTACH', 'me')
|
||||
comp.add('ATTACH', 'you', encode=False)
|
||||
binary = prop.vBinary('us')
|
||||
comp.add('ATTACH', binary)
|
||||
"""Already encoded values should not be re-encoded."""
|
||||
comp.add("ATTACH", "me")
|
||||
comp.add("ATTACH", "you", encode=False)
|
||||
binary = prop.vBinary("us")
|
||||
comp.add("ATTACH", binary)
|
||||
|
||||
assert comp['ATTACH'] == ['me', 'you', binary]
|
||||
assert comp["ATTACH"] == ["me", "you", binary]
|
||||
|
||||
|
||||
def test_cal_Component_add_property_parameter(comp):
|
||||
"""Test the for timezone correctness: dtstart should preserve it's
|
||||
timezone, crated, dtstamp and last-modified must be in UTC.
|
||||
"""
|
||||
comp.add('X-TEST-PROP', 'tryout.',
|
||||
parameters={'prop1': 'val1', 'prop2': 'val2'})
|
||||
comp.add("X-TEST-PROP", "tryout.", parameters={"prop1": "val1", "prop2": "val2"})
|
||||
lines = comp.to_ical().splitlines()
|
||||
assert b"X-TEST-PROP;PROP1=val1;PROP2=val2:tryout." in lines
|
||||
|
||||
|
|
@ -239,20 +275,20 @@ def test_cal_Component_add_property_parameter(comp):
|
|||
comp_prop = pytest.mark.parametrize(
|
||||
"component_name, property_name",
|
||||
[
|
||||
('VEVENT', 'DTSTART'),
|
||||
('VEVENT', 'DTEND'),
|
||||
('VEVENT', 'RECURRENCE-ID'),
|
||||
('VTODO', 'DUE')
|
||||
]
|
||||
("VEVENT", "DTSTART"),
|
||||
("VEVENT", "DTEND"),
|
||||
("VEVENT", "RECURRENCE-ID"),
|
||||
("VTODO", "DUE"),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@comp_prop
|
||||
def test_cal_Component_from_ical(component_name, property_name, tzp):
|
||||
"""Check for proper handling of TZID parameter of datetime properties"""
|
||||
component_str = 'BEGIN:' + component_name + '\n'
|
||||
component_str += property_name + ';TZID=America/Denver:'
|
||||
component_str += '20120404T073000\nEND:' + component_name
|
||||
component_str = "BEGIN:" + component_name + "\n"
|
||||
component_str += property_name + ";TZID=America/Denver:"
|
||||
component_str += "20120404T073000\nEND:" + component_name
|
||||
component = Component.from_ical(component_str)
|
||||
assert tzid_from_dt(component[property_name].dt) == "America/Denver"
|
||||
|
||||
|
|
@ -260,20 +296,22 @@ def test_cal_Component_from_ical(component_name, property_name, tzp):
|
|||
@comp_prop
|
||||
def test_cal_Component_from_ical_2(component_name, property_name, tzp):
|
||||
"""Check for proper handling of TZID parameter of datetime properties"""
|
||||
component_str = 'BEGIN:' + component_name + '\n'
|
||||
component_str += property_name + ':'
|
||||
component_str += '20120404T073000\nEND:' + component_name
|
||||
component_str = "BEGIN:" + component_name + "\n"
|
||||
component_str += property_name + ":"
|
||||
component_str += "20120404T073000\nEND:" + component_name
|
||||
component = Component.from_ical(component_str)
|
||||
assert component[property_name].dt.tzinfo == None
|
||||
|
||||
|
||||
def test_cal_Component_to_ical_property_order():
|
||||
component_str = [b'BEGIN:VEVENT',
|
||||
b'DTSTART:19970714T170000Z',
|
||||
b'DTEND:19970715T035959Z',
|
||||
b'SUMMARY:Bastille Day Party',
|
||||
b'END:VEVENT']
|
||||
component = Component.from_ical(b'\r\n'.join(component_str))
|
||||
component_str = [
|
||||
b"BEGIN:VEVENT",
|
||||
b"DTSTART:19970714T170000Z",
|
||||
b"DTEND:19970715T035959Z",
|
||||
b"SUMMARY:Bastille Day Party",
|
||||
b"END:VEVENT",
|
||||
]
|
||||
component = Component.from_ical(b"\r\n".join(component_str))
|
||||
|
||||
sorted_str = component.to_ical().splitlines()
|
||||
assert sorted_str != component_str
|
||||
|
|
@ -284,39 +322,43 @@ def test_cal_Component_to_ical_property_order():
|
|||
|
||||
|
||||
def test_cal_Component_to_ical_parameter_order():
|
||||
component_str = [b'BEGIN:VEVENT',
|
||||
b'X-FOOBAR;C=one;A=two;B=three:helloworld.',
|
||||
b'END:VEVENT']
|
||||
component = Component.from_ical(b'\r\n'.join(component_str))
|
||||
component_str = [
|
||||
b"BEGIN:VEVENT",
|
||||
b"X-FOOBAR;C=one;A=two;B=three:helloworld.",
|
||||
b"END:VEVENT",
|
||||
]
|
||||
component = Component.from_ical(b"\r\n".join(component_str))
|
||||
|
||||
sorted_str = component.to_ical().splitlines()
|
||||
assert sorted_str[0] == component_str[0]
|
||||
assert sorted_str[1] == b'X-FOOBAR;A=two;B=three;C=one:helloworld.'
|
||||
assert sorted_str[1] == b"X-FOOBAR;A=two;B=three;C=one:helloworld."
|
||||
assert sorted_str[2] == component_str[2]
|
||||
|
||||
preserved_str = component.to_ical(sorted=False).splitlines()
|
||||
assert preserved_str == component_str
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def repr_example(c):
|
||||
class ReprExample:
|
||||
component = c
|
||||
component['key1'] = 'value1'
|
||||
component["key1"] = "value1"
|
||||
calendar = Calendar()
|
||||
calendar['key1'] = 'value1'
|
||||
calendar["key1"] = "value1"
|
||||
event = Event()
|
||||
event['key1'] = 'value1'
|
||||
nested = Component(key1='VALUE1')
|
||||
event["key1"] = "value1"
|
||||
nested = Component(key1="VALUE1")
|
||||
nested.add_component(component)
|
||||
nested.add_component(calendar)
|
||||
|
||||
return ReprExample
|
||||
|
||||
|
||||
def test_repr_component(repr_example):
|
||||
"""Test correct class representation.
|
||||
"""
|
||||
"""Test correct class representation."""
|
||||
assert re.match(r"Component\({u?'KEY1': u?'value1'}\)", str(repr_example.component))
|
||||
|
||||
|
||||
def test_repr_calendar(repr_example):
|
||||
assert re.match(r"VCALENDAR\({u?'KEY1': u?'value1'}\)", str(repr_example.calendar))
|
||||
|
||||
|
|
@ -330,24 +372,24 @@ def test_nested_components(repr_example):
|
|||
repr_example.calendar.add_component(repr_example.event)
|
||||
print(repr_example.nested)
|
||||
assert re.match(
|
||||
r"Component\({u?'KEY1': u?'VALUE1'}, "
|
||||
r"Component\({u?'KEY1': u?'value1'}\), "
|
||||
r"VCALENDAR\({u?'KEY1': u?'value1'}, "
|
||||
r"VEVENT\({u?'KEY1': u?'value1'}\)\)\)",
|
||||
str(repr_example.nested)
|
||||
)
|
||||
r"Component\({u?'KEY1': u?'VALUE1'}, "
|
||||
r"Component\({u?'KEY1': u?'value1'}\), "
|
||||
r"VCALENDAR\({u?'KEY1': u?'value1'}, "
|
||||
r"VEVENT\({u?'KEY1': u?'value1'}\)\)\)",
|
||||
str(repr_example.nested),
|
||||
)
|
||||
|
||||
|
||||
def test_component_factory_VEVENT(factory):
|
||||
"""Check the events in the component factory"""
|
||||
component = factory['VEVENT']
|
||||
event = component(dtstart='19700101')
|
||||
assert event.to_ical() == b'BEGIN:VEVENT\r\nDTSTART:19700101\r\nEND:VEVENT\r\n'
|
||||
component = factory["VEVENT"]
|
||||
event = component(dtstart="19700101")
|
||||
assert event.to_ical() == b"BEGIN:VEVENT\r\nDTSTART:19700101\r\nEND:VEVENT\r\n"
|
||||
|
||||
|
||||
def test_component_factory_VCALENDAR(factory):
|
||||
"""Check the VCALENDAR in the factory."""
|
||||
assert factory.get('VCALENDAR') == icalendar.cal.Calendar
|
||||
assert factory.get("VCALENDAR") == icalendar.cal.Calendar
|
||||
|
||||
|
||||
def test_minimal_calendar_component_with_one_event():
|
||||
|
|
@ -355,19 +397,21 @@ def test_minimal_calendar_component_with_one_event():
|
|||
cal = Calendar()
|
||||
|
||||
# Some properties are required to be compliant
|
||||
cal['prodid'] = '-//My calendar product//mxm.dk//'
|
||||
cal['version'] = '2.0'
|
||||
cal["prodid"] = "-//My calendar product//mxm.dk//"
|
||||
cal["version"] = "2.0"
|
||||
|
||||
# We also need at least one subcomponent for a calendar to be compliant
|
||||
event = Event()
|
||||
event['summary'] = 'Python meeting about calendaring'
|
||||
event['uid'] = '42'
|
||||
event.add('dtstart', datetime(2005, 4, 4, 8, 0, 0))
|
||||
event["summary"] = "Python meeting about calendaring"
|
||||
event["uid"] = "42"
|
||||
event.add("dtstart", datetime(2005, 4, 4, 8, 0, 0))
|
||||
cal.add_component(event)
|
||||
assert cal.subcomponents[0].to_ical() == \
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Python meeting about calendaring\r\n' \
|
||||
+ b'DTSTART:20050404T080000\r\nUID:42\r\n' \
|
||||
+ b'END:VEVENT\r\n'
|
||||
assert (
|
||||
cal.subcomponents[0].to_ical()
|
||||
== b"BEGIN:VEVENT\r\nSUMMARY:Python meeting about calendaring\r\n"
|
||||
+ b"DTSTART:20050404T080000\r\nUID:42\r\n"
|
||||
+ b"END:VEVENT\r\n"
|
||||
)
|
||||
|
||||
|
||||
def test_calendar_with_parsing_errors_includes_all_events(calendars):
|
||||
|
|
@ -376,8 +420,10 @@ def test_calendar_with_parsing_errors_includes_all_events(calendars):
|
|||
attribute. The error in the following is the third EXDATE: it has an
|
||||
empty DATE.
|
||||
"""
|
||||
event_descriptions = [e['DESCRIPTION'].to_ical() for e in calendars.parsing_error.walk('VEVENT')]
|
||||
assert event_descriptions == [b'Perfectly OK event', b'Wrong event']
|
||||
event_descriptions = [
|
||||
e["DESCRIPTION"].to_ical() for e in calendars.parsing_error.walk("VEVENT")
|
||||
]
|
||||
assert event_descriptions == [b"Perfectly OK event", b"Wrong event"]
|
||||
|
||||
|
||||
def test_calendar_with_parsing_errors_has_an_error_in_one_event(calendars):
|
||||
|
|
@ -386,8 +432,8 @@ def test_calendar_with_parsing_errors_has_an_error_in_one_event(calendars):
|
|||
attribute. The error in the following is the third EXDATE: it has an
|
||||
empty DATE.
|
||||
"""
|
||||
errors = [e.errors for e in calendars.parsing_error.walk('VEVENT')]
|
||||
assert errors == [[], [('EXDATE', "Expected datetime, date, or time, got: ''")]]
|
||||
errors = [e.errors for e in calendars.parsing_error.walk("VEVENT")]
|
||||
assert errors == [[], [("EXDATE", "Expected datetime, date, or time, got: ''")]]
|
||||
|
||||
|
||||
def test_cal_strict_parsing(calendars):
|
||||
|
|
@ -398,21 +444,26 @@ def test_cal_strict_parsing(calendars):
|
|||
|
||||
def test_cal_ignore_errors_parsing(calendars, vUTCOffset_ignore_exceptions):
|
||||
"""If we diable the errors, we should be able to put the calendar back together."""
|
||||
assert calendars.parsing_error_in_UTC_offset.to_ical() == calendars.parsing_error_in_UTC_offset.raw_ics
|
||||
|
||||
assert (
|
||||
calendars.parsing_error_in_UTC_offset.to_ical()
|
||||
== calendars.parsing_error_in_UTC_offset.raw_ics
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'calendar, other_calendar',
|
||||
itertools.product([
|
||||
'issue_156_RDATE_with_PERIOD_TZID_khal',
|
||||
'issue_156_RDATE_with_PERIOD_TZID_khal_2',
|
||||
'issue_178_custom_component_contains_other',
|
||||
'issue_178_custom_component_inside_other',
|
||||
'issue_526_calendar_with_events',
|
||||
'issue_526_calendar_with_different_events',
|
||||
'issue_526_calendar_with_event_subset',
|
||||
], repeat=2)
|
||||
("calendar", "other_calendar"),
|
||||
itertools.product(
|
||||
[
|
||||
"issue_156_RDATE_with_PERIOD_TZID_khal",
|
||||
"issue_156_RDATE_with_PERIOD_TZID_khal_2",
|
||||
"issue_178_custom_component_contains_other",
|
||||
"issue_178_custom_component_inside_other",
|
||||
"issue_526_calendar_with_events",
|
||||
"issue_526_calendar_with_different_events",
|
||||
"issue_526_calendar_with_event_subset",
|
||||
],
|
||||
repeat=2,
|
||||
),
|
||||
)
|
||||
def test_comparing_calendars(calendars, calendar, other_calendar, tzp):
|
||||
are_calendars_equal = calendars[calendar] == calendars[other_calendar]
|
||||
|
|
@ -420,12 +471,19 @@ def test_comparing_calendars(calendars, calendar, other_calendar, tzp):
|
|||
assert are_calendars_equal == are_calendars_actually_equal
|
||||
|
||||
|
||||
@pytest.mark.parametrize('calendar, shuffeled_calendar', [
|
||||
(
|
||||
'issue_526_calendar_with_events',
|
||||
'issue_526_calendar_with_shuffeled_events',
|
||||
),
|
||||
])
|
||||
def test_calendars_with_same_subcomponents_in_different_order_are_equal(calendars, calendar, shuffeled_calendar):
|
||||
assert not calendars[calendar].subcomponents == calendars[shuffeled_calendar].subcomponents
|
||||
@pytest.mark.parametrize(
|
||||
("calendar", "shuffeled_calendar"),
|
||||
[
|
||||
(
|
||||
"issue_526_calendar_with_events",
|
||||
"issue_526_calendar_with_shuffeled_events",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_calendars_with_same_subcomponents_in_different_order_are_equal(
|
||||
calendars, calendar, shuffeled_calendar
|
||||
):
|
||||
assert (
|
||||
calendars[calendar].subcomponents != calendars[shuffeled_calendar].subcomponents
|
||||
)
|
||||
assert calendars[calendar] == calendars[shuffeled_calendar]
|
||||
|
|
|
|||
|
|
@ -4,64 +4,93 @@ import icalendar
|
|||
|
||||
|
||||
class TestCaselessdict(unittest.TestCase):
|
||||
|
||||
def test_caselessdict_canonsort_keys(self):
|
||||
canonsort_keys = icalendar.caselessdict.canonsort_keys
|
||||
|
||||
keys = ['DTEND', 'DTSTAMP', 'DTSTART', 'UID', 'SUMMARY', 'LOCATION']
|
||||
keys = ["DTEND", "DTSTAMP", "DTSTART", "UID", "SUMMARY", "LOCATION"]
|
||||
|
||||
out = canonsort_keys(keys)
|
||||
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']
|
||||
)
|
||||
|
||||
out = canonsort_keys(keys, ('UID', 'DTSTART', 'DTEND', ))
|
||||
self.assertEqual(
|
||||
out,
|
||||
['UID', 'DTSTART', 'DTEND', 'DTSTAMP', 'LOCATION', 'SUMMARY']
|
||||
out, ["DTEND", "DTSTAMP", "DTSTART", "LOCATION", "SUMMARY", "UID"]
|
||||
)
|
||||
|
||||
out = canonsort_keys(
|
||||
keys,
|
||||
('UID', 'DTSTART', 'DTEND', 'RRULE', 'EXDATE')
|
||||
(
|
||||
"SUMMARY",
|
||||
"DTSTART",
|
||||
"DTEND",
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
out,
|
||||
['UID', 'DTSTART', 'DTEND', 'DTSTAMP', 'LOCATION', 'SUMMARY']
|
||||
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"]
|
||||
)
|
||||
|
||||
def test_caselessdict_canonsort_items(self):
|
||||
canonsort_items = icalendar.caselessdict.canonsort_items
|
||||
|
||||
d = {
|
||||
'i': 7, 'c': 'at', 'a': 3.5, 'l': (2, 3), 'e': [4, 5], 'n': 13, 'd': {'x': 'y'}, 'r': 1.0,
|
||||
"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,
|
||||
[('a', 3.5), ('c', 'at'), ('d', {'x': 'y'}), ('e', [4, 5]),
|
||||
('i', 7), ('l', (2, 3)), ('n', 13), ('r', 1.0)]
|
||||
[
|
||||
("a", 3.5),
|
||||
("c", "at"),
|
||||
("d", {"x": "y"}),
|
||||
("e", [4, 5]),
|
||||
("i", 7),
|
||||
("l", (2, 3)),
|
||||
("n", 13),
|
||||
("r", 1.0),
|
||||
],
|
||||
)
|
||||
|
||||
out = canonsort_items(d, ('i', 'c', 'a'))
|
||||
out = canonsort_items(d, ("i", "c", "a"))
|
||||
self.assertTrue(
|
||||
out,
|
||||
[('i', 7), ('c', 'at'), ('a', 3.5), ('d', {'x': 'y'}),
|
||||
('e', [4, 5]), ('l', (2, 3)), ('n', 13), ('r', 1.0)]
|
||||
[
|
||||
("i", 7),
|
||||
("c", "at"),
|
||||
("a", 3.5),
|
||||
("d", {"x": "y"}),
|
||||
("e", [4, 5]),
|
||||
("l", (2, 3)),
|
||||
("n", 13),
|
||||
("r", 1.0),
|
||||
],
|
||||
)
|
||||
|
||||
def test_caselessdict_copy(self):
|
||||
CaselessDict = icalendar.caselessdict.CaselessDict
|
||||
|
||||
original_dict = CaselessDict(key1='val1', key2='val2')
|
||||
original_dict = CaselessDict(key1="val1", key2="val2")
|
||||
copied_dict = original_dict.copy()
|
||||
|
||||
self.assertEqual(original_dict, copied_dict)
|
||||
|
|
@ -69,31 +98,28 @@ class TestCaselessdict(unittest.TestCase):
|
|||
def test_CaselessDict(self):
|
||||
CaselessDict = icalendar.caselessdict.CaselessDict
|
||||
|
||||
ncd = CaselessDict(key1='val1', key2='val2')
|
||||
self.assertEqual(
|
||||
ncd,
|
||||
CaselessDict({'KEY2': 'val2', 'KEY1': 'val1'})
|
||||
)
|
||||
ncd = CaselessDict(key1="val1", key2="val2")
|
||||
self.assertEqual(ncd, CaselessDict({"KEY2": "val2", "KEY1": "val1"}))
|
||||
|
||||
self.assertEqual(ncd['key1'], 'val1')
|
||||
self.assertEqual(ncd['KEY1'], 'val1')
|
||||
self.assertEqual(ncd["key1"], "val1")
|
||||
self.assertEqual(ncd["KEY1"], "val1")
|
||||
|
||||
ncd['KEY3'] = 'val3'
|
||||
self.assertEqual(ncd['key3'], 'val3')
|
||||
ncd["KEY3"] = "val3"
|
||||
self.assertEqual(ncd["key3"], "val3")
|
||||
|
||||
self.assertEqual(ncd.setdefault('key3', 'FOUND'), 'val3')
|
||||
self.assertEqual(ncd.setdefault('key4', 'NOT FOUND'), 'NOT FOUND')
|
||||
self.assertEqual(ncd['key4'], 'NOT FOUND')
|
||||
self.assertEqual(ncd.get('key1'), 'val1')
|
||||
self.assertEqual(ncd.get('key3', 'NOT FOUND'), 'val3')
|
||||
self.assertEqual(ncd.get('key4', 'NOT FOUND'), 'NOT FOUND')
|
||||
self.assertTrue('key4' in ncd)
|
||||
self.assertEqual(ncd.setdefault("key3", "FOUND"), "val3")
|
||||
self.assertEqual(ncd.setdefault("key4", "NOT FOUND"), "NOT FOUND")
|
||||
self.assertEqual(ncd["key4"], "NOT FOUND")
|
||||
self.assertEqual(ncd.get("key1"), "val1")
|
||||
self.assertEqual(ncd.get("key3", "NOT FOUND"), "val3")
|
||||
self.assertEqual(ncd.get("key4", "NOT FOUND"), "NOT FOUND")
|
||||
self.assertTrue("key4" in ncd)
|
||||
|
||||
del ncd['key4']
|
||||
self.assertFalse('key4' in ncd)
|
||||
del ncd["key4"]
|
||||
self.assertFalse("key4" in ncd)
|
||||
|
||||
ncd.update({'key5': 'val5', 'KEY6': 'val6', 'KEY5': 'val7'})
|
||||
self.assertEqual(ncd['key6'], 'val6')
|
||||
ncd.update({"key5": "val5", "KEY6": "val6", "KEY5": "val7"})
|
||||
self.assertEqual(ncd["key6"], "val6")
|
||||
|
||||
keys = sorted(ncd.keys())
|
||||
self.assertEqual(keys, ['KEY1', 'KEY2', 'KEY3', 'KEY5', 'KEY6'])
|
||||
self.assertEqual(keys, ["KEY1", "KEY2", "KEY3", "KEY5", "KEY6"])
|
||||
|
|
|
|||
|
|
@ -1,28 +1,30 @@
|
|||
from icalendar.parser_tools import data_encode
|
||||
from icalendar.parser_tools import to_unicode
|
||||
import unittest
|
||||
|
||||
from icalendar.parser_tools import data_encode, to_unicode
|
||||
|
||||
|
||||
class TestParserTools(unittest.TestCase):
|
||||
|
||||
def test_parser_tools_to_unicode(self):
|
||||
|
||||
self.assertEqual(to_unicode(b'spam'), 'spam')
|
||||
self.assertEqual(to_unicode('spam'), 'spam')
|
||||
self.assertEqual(to_unicode(b'spam'), 'spam')
|
||||
self.assertEqual(to_unicode(b'\xc6\xb5'), '\u01b5')
|
||||
self.assertEqual(to_unicode(b'\xc6\xb5'),
|
||||
'\u01b5')
|
||||
self.assertEqual(to_unicode(b'\xc6\xb5', encoding='ascii'), '\u01b5')
|
||||
self.assertEqual(to_unicode(b"spam"), "spam")
|
||||
self.assertEqual(to_unicode("spam"), "spam")
|
||||
self.assertEqual(to_unicode(b"spam"), "spam")
|
||||
self.assertEqual(to_unicode(b"\xc6\xb5"), "\u01b5")
|
||||
self.assertEqual(to_unicode(b"\xc6\xb5"), "\u01b5")
|
||||
self.assertEqual(to_unicode(b"\xc6\xb5", encoding="ascii"), "\u01b5")
|
||||
self.assertEqual(to_unicode(1), 1)
|
||||
self.assertEqual(to_unicode(None), None)
|
||||
|
||||
def test_parser_tools_data_encode(self):
|
||||
|
||||
data1 = {
|
||||
'k1': 'v1', 'k2': 'v2', 'k3': 'v3',
|
||||
'li1': ['it1', 'it2', {'k4': 'v4', 'k5': 'v5'}, 123]
|
||||
"k1": "v1",
|
||||
"k2": "v2",
|
||||
"k3": "v3",
|
||||
"li1": ["it1", "it2", {"k4": "v4", "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],
|
||||
}
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import pytest
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from icalendar.tools import UIDGenerator
|
||||
|
||||
|
||||
class TestTools(unittest.TestCase):
|
||||
|
||||
def test_tools_UIDGenerator(self):
|
||||
|
||||
# Automatic semi-random uid
|
||||
g = UIDGenerator()
|
||||
uid = g.uid()
|
||||
|
|
@ -14,41 +14,68 @@ class TestTools(unittest.TestCase):
|
|||
txt = uid.to_ical()
|
||||
length = 15 + 1 + 16 + 1 + 11
|
||||
self.assertTrue(len(txt) == length)
|
||||
self.assertTrue(b'@example.com' in txt)
|
||||
self.assertTrue(b"@example.com" in txt)
|
||||
|
||||
# You should at least insert your own hostname to be more compliant
|
||||
uid = g.uid('Example.ORG')
|
||||
uid = g.uid("Example.ORG")
|
||||
txt = uid.to_ical()
|
||||
self.assertTrue(len(txt) == length)
|
||||
self.assertTrue(b'@Example.ORG' in txt)
|
||||
self.assertTrue(b"@Example.ORG" in txt)
|
||||
|
||||
# You can also insert a path or similar
|
||||
uid = g.uid('Example.ORG', '/path/to/content')
|
||||
uid = g.uid("Example.ORG", "/path/to/content")
|
||||
txt = uid.to_ical()
|
||||
self.assertTrue(len(txt) == length)
|
||||
self.assertTrue(b'-/path/to/content@Example.ORG' in txt)
|
||||
self.assertTrue(b"-/path/to/content@Example.ORG" in txt)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('split,expected,args,kw', [
|
||||
# default argument host_name
|
||||
("@", "example.com", (), {},),
|
||||
("@", "example.com", ("example.com",), {}),
|
||||
("@", "example.com", (), {"host_name":"example.com"}),
|
||||
# replaced host_name
|
||||
("@", "test.test", ("test.test",), {}),
|
||||
("@", "test.test", (), {"host_name":"test.test"}),
|
||||
# replace unique
|
||||
("-", "123@example.com", (), {"unique": "123"},),
|
||||
("-", "abc@example.com", (), {"unique": "abc"},),
|
||||
# replace host_name and unique
|
||||
("-", "1234@test.icalendar", (), {"unique": "1234", "host_name":"test.icalendar"},),
|
||||
("-", "abc@test.example.com", ("test.example.com", "abc"), {},),
|
||||
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
("split", "expected", "args", "kw"),
|
||||
[
|
||||
# default argument host_name
|
||||
(
|
||||
"@",
|
||||
"example.com",
|
||||
(),
|
||||
{},
|
||||
),
|
||||
("@", "example.com", ("example.com",), {}),
|
||||
("@", "example.com", (), {"host_name": "example.com"}),
|
||||
# replaced host_name
|
||||
("@", "test.test", ("test.test",), {}),
|
||||
("@", "test.test", (), {"host_name": "test.test"}),
|
||||
# replace unique
|
||||
(
|
||||
"-",
|
||||
"123@example.com",
|
||||
(),
|
||||
{"unique": "123"},
|
||||
),
|
||||
(
|
||||
"-",
|
||||
"abc@example.com",
|
||||
(),
|
||||
{"unique": "abc"},
|
||||
),
|
||||
# replace host_name and unique
|
||||
(
|
||||
"-",
|
||||
"1234@test.icalendar",
|
||||
(),
|
||||
{"unique": "1234", "host_name": "test.icalendar"},
|
||||
),
|
||||
(
|
||||
"-",
|
||||
"abc@test.example.com",
|
||||
("test.example.com", "abc"),
|
||||
{},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_uid_generator_issue_345(args, kw, split, expected):
|
||||
'''Issue #345 - Why is tools.UIDGenerator a class (that must be instantiated) instead of a module?
|
||||
"""Issue #345 - Why is tools.UIDGenerator a class (that must be instantiated) instead of a module?
|
||||
|
||||
see https://github.com/collective/icalendar/issues/345
|
||||
'''
|
||||
"""
|
||||
uid = UIDGenerator.uid(*args, **kw)
|
||||
assert uid.split(split)[1] == expected
|
||||
|
|
|
|||
|
|
@ -10,12 +10,13 @@ This file should be tests, too:
|
|||
Hello World!
|
||||
|
||||
"""
|
||||
|
||||
import doctest
|
||||
import os
|
||||
import pytest
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = os.path.dirname(__file__) or "."
|
||||
ICALENDAR_PATH = os.path.dirname(HERE)
|
||||
|
|
@ -23,17 +24,21 @@ ICALENDAR_PATH = os.path.dirname(HERE)
|
|||
PYTHON_FILES = [
|
||||
"/".join((dirpath, filename))
|
||||
for dirpath, dirnames, filenames in os.walk(ICALENDAR_PATH)
|
||||
for filename in filenames if filename.lower().endswith(".py") and 'fuzzing' not in dirpath
|
||||
for filename in filenames
|
||||
if filename.lower().endswith(".py") and "fuzzing" not in dirpath
|
||||
]
|
||||
|
||||
MODULE_NAMES = [
|
||||
"icalendar" + python_file[len(ICALENDAR_PATH):-3].replace("\\", "/").replace("/", ".")
|
||||
"icalendar"
|
||||
+ python_file[len(ICALENDAR_PATH) : -3].replace("\\", "/").replace("/", ".")
|
||||
for python_file in PYTHON_FILES
|
||||
]
|
||||
|
||||
|
||||
def test_this_module_is_among_them():
|
||||
assert __name__ in MODULE_NAMES
|
||||
|
||||
|
||||
@pytest.mark.parametrize("module_name", MODULE_NAMES)
|
||||
def test_docstring_of_python_file(module_name, env_for_doctest):
|
||||
"""This test runs doctest on the Python module."""
|
||||
|
|
@ -58,12 +63,18 @@ try:
|
|||
if filename.lower().endswith(".rst")
|
||||
]
|
||||
except FileNotFoundError:
|
||||
raise OSError("Could not find the documentation - remove the build folder and try again.")
|
||||
raise OSError(
|
||||
"Could not find the documentation - remove the build folder and try again."
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("filename", [
|
||||
"README.rst",
|
||||
"index.rst",
|
||||
])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filename",
|
||||
[
|
||||
"README.rst",
|
||||
"index.rst",
|
||||
],
|
||||
)
|
||||
def test_files_is_included(filename):
|
||||
assert any(path.endswith(filename) for path in DOCUMENT_PATHS)
|
||||
|
||||
|
|
@ -76,14 +87,18 @@ def test_documentation_file(document, zoneinfo_only, env_for_doctest, tzp):
|
|||
"""
|
||||
try:
|
||||
# set raise_on_error to False if you wand to see the error for debug
|
||||
test_result = doctest.testfile(document, module_relative=False, globs=env_for_doctest, raise_on_error=True)
|
||||
test_result = doctest.testfile(
|
||||
document, module_relative=False, globs=env_for_doctest, raise_on_error=True
|
||||
)
|
||||
except doctest.UnexpectedException as e:
|
||||
ty, err, tb = e.exc_info
|
||||
if issubclass(ty, ModuleNotFoundError) and err.name == "pytz":
|
||||
pytest.skip("pytz not installed, skipping this file.")
|
||||
finally:
|
||||
tzp.use_zoneinfo()
|
||||
assert test_result.failed == 0, f"{test_result.failed} errors in {os.path.basename(document)}"
|
||||
assert (
|
||||
test_result.failed == 0
|
||||
), f"{test_result.failed} errors in {os.path.basename(document)}"
|
||||
|
||||
|
||||
def test_can_import_zoneinfo(env_for_doctest):
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue