Merge pull request #565 from jacadzaca/issue-27

Issue 27
issue-27
Nicco Kunzmann 2023-10-02 18:47:57 +01:00 zatwierdzone przez GitHub
commit 4cb7b121a4
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
25 zmienionych plików z 555 dodań i 50 usunięć

Wyświetl plik

@ -1,14 +1,14 @@
Changelog
=========
5.0.8 (unreleased)
------------------
5.0.11 (unreleased)
-------------------
Minor changes:
- Update build configuration to build readthedocs. #538
- No longer run the ``plone.app.event`` tests.
- Move pip caching into Python setup action.
- The cli utility now displays start and end datetimes in the user's local timezone.
Ref: #561
[vimpostor]
Breaking changes:
@ -20,7 +20,45 @@ New features:
Bug fixes:
- ...
- Multivalue FREEBUSY property is now parsed properly
Ref: #27
[jacadzaca]
5.0.10 (unreleased)
-------------------
Bug fixes:
- Component._encode stops ignoring parameters argument on native values, now merges them
Fixes: #557
[zocker1999net]
5.0.9 (2023-09-24)
------------------
Bug fixes:
- PERIOD values now set the timezone of their start and end. #556
5.0.8 (2023-09-18)
------------------
Minor changes:
- Update build configuration to build readthedocs. #538
- No longer run the ``plone.app.event`` tests.
- Add documentation on how to parse ``.ics`` files. #152
- Move pip caching into Python setup action.
- Check that issue #165 can be closed.
- Updated about.rst for issue #527
- Avoid ``vText.__repr__`` BytesWarning.
Bug fixes:
- Calendar components are now properly compared
Ref: #550
Fixes: #526
[jacadzaca]
5.0.7 (2023-05-29)
------------------

Wyświetl plik

@ -42,6 +42,27 @@ files.
.. _`pytz`: https://pypi.org/project/pytz/
.. _`BSD`: https://github.com/collective/icalendar/issues/2
Quick Guide
-----------
To **install** the package, run::
pip install icalendar
You can open an ``.ics`` file and see all the events::
>>> import icalendar
>>> path_to_ics_file = "src/icalendar/tests/calendars/example.ics"
>>> with open(path_to_ics_file) as f:
... calendar = icalendar.Calendar.from_ical(f.read())
>>> for event in calendar.walk('VEVENT'):
... print(event.get("SUMMARY"))
New Year's Day
Orthodox Christmas
International Women's Day
Using this package, you can also create calendars from scratch or edit existing ones.
Versions and Compatibility
--------------------------

Wyświetl plik

@ -1,15 +1,13 @@
About
=====
`Max M`_ had often needed to parse and generate iCalendar files. Finally he got
`Max M`_ had often needed to parse and generate iCalendar files. Finally, he got
tired of writing ad-hoc tools. This package is his attempt at making an
iCalendar package for Python. The inspiration has come from the email package
in the standard lib, which he thinks is pretty simple, yet efficient and
powerful.
At the time of writing this, last version was released more then 2 years ago.
Since then many things have changes. For one, `RFC 2445`_ was updated by `RFC
5545`_ which makes this package. So in some sense this package became outdated.
The icalendar package is an RFC 5545-compatible parser/generator for iCalendar files.
.. _`Max M`: http://www.mxm.dk
.. _`RFC 2445`: https://tools.ietf.org/html/rfc2445

Wyświetl plik

@ -43,6 +43,7 @@ icalendar contributors
- Thomas Bruederli <thomas@roundcube.net>
- Thomas Weißschuh <thomas@t-8ch.de>
- Victor Varvaryuk <victor.varvariuc@gmail.com>
- Ville Skyttä <ville.skytta@iki.fi>
- Wichert Akkerman <wichert@wiggy.net>
- cillianderoiste <cillian.deroiste@gmail.com>
- fitnr <fitnr@fakeisthenewreal>
@ -69,6 +70,7 @@ icalendar contributors
- `Natasha Mattson <https://github.com/natashamm`_
- `NikEasY <https://github.com/NikEasY>`_
- Matt Lewis <git@semiprime.com>
- Felix Stupp <felix.stupp@banananet.work>
Find out who contributed::

Wyświetl plik

@ -112,7 +112,7 @@ Try it out:
Type "help", "copyright", "credits" or "license" for more information.
>>> import icalendar
>>> icalendar.__version__
'5.0.7'
'5.0.10'
Building the documentation locally
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Wyświetl plik

@ -342,5 +342,5 @@ Print out the calendar::
More documentation
==================
Have a look at the tests of this package to get more examples.
Have a look at the `tests <https://github.com/collective/icalendar/tree/master/src/icalendar/tests>`__ of this package to get more examples.
All modules and classes docstrings, which document how they work.

Wyświetl plik

@ -1,4 +1,4 @@
__version__ = '5.0.7'
__version__ = '5.0.10'
from icalendar.cal import (
Calendar,

Wyświetl plik

@ -113,7 +113,8 @@ class Component(CaselessDict):
#############################
# handling of property values
def _encode(self, name, value, parameters=None, encode=1):
@staticmethod
def _encode(name, value, parameters=None, encode=1):
"""Encode values to icalendar property values.
:param name: Name of the property.
@ -138,17 +139,19 @@ class Component(CaselessDict):
return value
if isinstance(value, types_factory.all_types):
# Don't encode already encoded values.
return value
klass = types_factory.for_property(name)
obj = klass(value)
obj = value
else:
klass = types_factory.for_property(name)
obj = klass(value)
if parameters:
if isinstance(parameters, dict):
params = Parameters()
for key, item in parameters.items():
params[key] = item
parameters = params
assert isinstance(parameters, Parameters)
obj.params = parameters
if not hasattr(obj, "params"):
obj.params = Parameters()
for key, item in parameters.items():
if item is None:
if key in obj.params:
del obj.params[key]
else:
obj.params[key] = item
return obj
def add(self, name, value, parameters=None, encode=1):
@ -372,19 +375,26 @@ class Component(CaselessDict):
if not component:
raise ValueError(f'Property "{name}" does not have a parent component.')
datetime_names = ('DTSTART', 'DTEND', 'RECURRENCE-ID', 'DUE',
'FREEBUSY', 'RDATE', 'EXDATE')
'RDATE', 'EXDATE')
try:
if name in datetime_names and 'TZID' in params:
vals = factory(factory.from_ical(vals, params['TZID']))
if name == 'FREEBUSY':
vals = vals.split(',')
if 'TZID' in params:
parsed_components = [factory(factory.from_ical(val, params['TZID'])) for val in vals]
else:
parsed_components = [factory(factory.from_ical(val)) for val in vals]
elif name in datetime_names and 'TZID' in params:
parsed_components = [factory(factory.from_ical(vals, params['TZID']))]
else:
vals = factory(factory.from_ical(vals))
parsed_components = [factory(factory.from_ical(vals))]
except ValueError as e:
if not component.ignore_exceptions:
raise
component.errors.append((uname, str(e)))
else:
vals.params = params
component.add(name, vals, encode=0)
for parsed_component in parsed_components:
parsed_component.params = params
component.add(name, parsed_component, encode=0)
if multiple:
return comps
@ -436,6 +446,25 @@ class Component(CaselessDict):
subs = ', '.join(str(it) for it in self.subcomponents)
return f"{self.name or type(self).__name__}({dict(self)}{', ' + subs if subs else ''})"
def __eq__(self, other):
if not len(self.subcomponents) == len(other.subcomponents):
return False
properties_equal = super().__eq__(other)
if not properties_equal:
return False
# The subcomponents might not be in the same order,
# neither there's a natural key we can sort the subcomponents by nor
# are the subcomponent types hashable, so we cant put them in a set to
# check for set equivalence. We have to iterate over the subcomponents
# and look for each of them in the list.
for subcomponent in self.subcomponents:
if subcomponent not in other.subcomponents:
return False
return True
#######################################
# components defined in RFC 5545

Wyświetl plik

@ -52,10 +52,10 @@ def view(event):
end = event.decoded('dtend', default=start)
duration = event.decoded('duration', default=end - start)
if isinstance(start, datetime):
start = start.astimezone(start.tzinfo)
start = start.astimezone()
start = start.strftime('%c')
if isinstance(end, datetime):
end = end.astimezone(end.tzinfo)
end = end.astimezone()
end = end.strftime('%c')
return f""" Organizer: {organizer}

Wyświetl plik

@ -331,7 +331,7 @@ class vDDDTypes:
if u.startswith(('P', '-P', '+P')):
return vDuration.from_ical(ical)
if '/' in u:
return vPeriod.from_ical(ical)
return vPeriod.from_ical(ical, timezone=timezone)
if len(ical) in (15, 16):
return vDatetime.from_ical(ical, timezone=timezone)
@ -533,6 +533,11 @@ class vPeriod:
f'Cannot compare vPeriod with {other!r}')
return cmp((self.start, self.end), (other.start, other.end))
def __eq__(self, other):
if not isinstance(other, vPeriod):
return False
return (self.start, self.end) == (other.start, other.end)
def overlaps(self, other):
if self.start > other.start:
return other.overlaps(self)
@ -548,11 +553,11 @@ class vPeriod:
+ vDatetime(self.end).to_ical())
@staticmethod
def from_ical(ical):
def from_ical(ical, timezone=None):
try:
start, end_or_duration = ical.split('/')
start = vDDDTypes.from_ical(start)
end_or_duration = vDDDTypes.from_ical(end_or_duration)
start = vDDDTypes.from_ical(start, timezone=timezone)
end_or_duration = vDDDTypes.from_ical(end_or_duration, timezone=timezone)
return (start, end_or_duration)
except Exception:
raise ValueError(f'Expected period format, got: {ical}')
@ -719,7 +724,7 @@ class vText(str):
return self
def __repr__(self):
return f"vText('{self.to_ical()}')"
return f"vText('{self.to_ical()!r}')"
def to_ical(self):
return escape_char(self).encode(self.encoding)

Wyświetl plik

@ -0,0 +1,40 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:collective/icalendar
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:Holidays
X-WR-TIMEZONE:Etc/GMT
BEGIN:VEVENT
SUMMARY:New Year's Day
DTSTART:20220101
DTEND:20220101
DESCRIPTION:Happy New Year!
UID:636a0cc1dbd5a1667894465@icalendar
DTSTAMP:20221108T080105Z
STATUS:CONFIRMED
TRANSP:TRANSPARENT
SEQUENCE:0
END:VEVENT
BEGIN:VEVENT
SUMMARY:Orthodox Christmas
DTSTART:20220107
DTEND:20220107
LOCATION:Russia
DESCRIPTION:It is Christmas again!
UID:636a0cc1dbfd91667894465@icalendar
STATUS:CONFIRMED
TRANSP:TRANSPARENT
SEQUENCE:0
END:VEVENT
BEGIN:VEVENT
SUMMARY:International Women's Day
DTSTART:20220308
DTEND:20220308
DESCRIPTION:May the feminine be honoured!
UID:636a0cc1dc0f11667894465@icalendar
STATUS:CONFIRMED
TRANSP:TRANSPARENT
SEQUENCE:0
END:VEVENT
END:VCALENDAR

Wyświetl plik

@ -0,0 +1,28 @@
BEGIN:VCALENDAR
METHOD:REQUEST
PRODID:Microsoft CDO for Microsoft Exchange
VERSION:2.0
BEGIN:VTIMEZONE
TZID:GMT +0100 (Standard) / GMT +0200 (Daylight)
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20150703T071009Z
DTSTART;TZID="GMT +0100 (Standard) / GMT +0200 (Daylight)":20150703T100000
SUMMARY:Sprint 25 Daily Standup
DTEND;TZID="GMT +0100 (Standard) / GMT +0200 (Daylight)":20150703T103000
RRULE:FREQ=DAILY;UNTIL=20150722T080000Z;INTERVAL=1;BYDAY=MO, TU, WE, TH, FR
;WKST=SU
END:VEVENT
END:VCALENDAR

Wyświetl plik

@ -0,0 +1,21 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//davmail.sf.net/NONSGML DavMail Calendar V1.1//EN
METHOD:REPLY
BEGIN:VFREEBUSY
DTSTAMP:20120131T123000Z
ORGANIZER:MAILTO:organizer@domain.tld
DTSTART:20120101T000000Z
DTEND:20120201T000000Z
UID:null
ATTENDEE:MAILTO:attendee@domain.tld
FREEBUSY;FBTYPE=BUSY:20120103T091500Z/20120103T101500Z
FREEBUSY;FBTYPE=BUSY:20120113T130000Z/20120113T150000Z
FREEBUSY;FBTYPE=BUSY:20120116T130000Z/20120116T150000Z
FREEBUSY;FBTYPE=BUSY:20120117T091500Z/20120117T101500Z
FREEBUSY;FBTYPE=BUSY:20120118T160000Z/20120118T163000Z
FREEBUSY;FBTYPE=BUSY:20120124T083000Z/20120124T093000Z
FREEBUSY;FBTYPE=BUSY:20120124T123000Z/20120124T143000Z
FREEBUSY;FBTYPE=BUSY:20120131T091500Z/20120131T101500Z
END:VFREEBUSY
END:VCALENDAR

Wyświetl plik

@ -0,0 +1,18 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:icalendar-2023
BEGIN:VEVENT
UID:ical-jacadzaca-3
SUMMARY: Some very different event ':'
DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000
DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000
DTSTAMP:20211004T150245Z
END:VEVENT
BEGIN:VEVENT
UID:ical-jacadzaca-4
SUMMARY: Some very different other event
DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T164000
DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T165000
DTSTAMP:20211004T150245Z
END:VEVENT
END:VCALENDAR

Wyświetl plik

@ -0,0 +1,11 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:icalendar-2023
BEGIN:VEVENT
UID:1
SUMMARY: Some event ':'
DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000
DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000
DTSTAMP:20211004T150245Z
END:VEVENT
END:VCALENDAR

Wyświetl plik

@ -0,0 +1,18 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:icalendar-2023
BEGIN:VEVENT
UID:1
SUMMARY: Some event ':'
DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000
DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000
DTSTAMP:20211004T150245Z
END:VEVENT
BEGIN:VEVENT
UID:2
SUMMARY: Some other event
DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T164000
DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T165000
DTSTAMP:20211004T150245Z
END:VEVENT
END:VCALENDAR

Wyświetl plik

@ -0,0 +1,18 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:icalendar-2023
BEGIN:VEVENT
UID:2
SUMMARY: Some other event
DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T164000
DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T165000
DTSTAMP:20211004T150245Z
END:VEVENT
BEGIN:VEVENT
UID:1
SUMMARY: Some event ':'
DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000
DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000
DTSTAMP:20211004T150245Z
END:VEVENT
END:VCALENDAR

Wyświetl plik

@ -0,0 +1,32 @@
BEGIN:VCALENDAR
VERSION:2.0
X-WR-CALNAME;VALUE=TEXT:Test RDATE
BEGIN:VTIMEZONE
TZID:America/Vancouver
BEGIN:STANDARD
DTSTART:20221106T020000
TZOFFSETFROM:-0700
TZOFFSETTO:-0800
RDATE:20231105T020000
TZNAME:PST
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:20230312T020000
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
RDATE:20240310T020000
TZNAME:PDT
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:1
DESCRIPTION:Test RDATE
DTSTART;TZID=America/Vancouver:20230920T120000
DTEND;TZID=America/Vancouver:20230920T140000
EXDATE;TZID=America/Vancouver:20231220T120000
RDATE;VALUE=PERIOD;TZID=America/Vancouver:20231213T120000/20231213T150000
RRULE:FREQ=MONTHLY;COUNT=9;INTERVAL=1;BYDAY=+3WE;BYMONTH=1,2,3,4,5,9,10,11,
12;WKST=MO
SUMMARY:Test RDATE
END:VEVENT
END:VCALENDAR

Wyświetl plik

@ -1,6 +1,11 @@
import unittest
from datetime import tzinfo, datetime
from icalendar import Calendar, cli
try:
import zoneinfo
except ModuleNotFoundError:
from backports import zoneinfo
INPUT = '''
BEGIN:VCALENDAR
@ -21,7 +26,7 @@ BEGIN:VEVENT
ORGANIZER:organizer@test.test
ATTENDEE:attendee1@example.com
ATTENDEE:attendee2@test.test
SUMMARY:Test summury
SUMMARY:Test summary
DTSTART;TZID=Europe/Warsaw:20220820T200000
DTEND;TZID=Europe/Warsaw:20220820T203000
LOCATION:New Amsterdam, 1010 Test Street
@ -36,13 +41,22 @@ END:VEVENT
END:VCALENDAR
'''
PROPER_OUTPUT = ''' Organizer: organizer <organizer@test.test>
def local_datetime(dt):
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')
PROPER_OUTPUT = f""" Organizer: organizer <organizer@test.test>
Attendees:
attendee1 <attendee1@example.com>
attendee2 <attendee2@test.test>
Summary : Test Summary
Starts : Sat Aug 20 10:34:00 2022
End : Sat Aug 20 11:34:00 2022
Starts : {firststart}
End : {firstend}
Duration : 1:00:00
Location : New Amsterdam, 1000 Sunrise Test Street
Comment : Comment
@ -53,9 +67,9 @@ PROPER_OUTPUT = ''' Organizer: organizer <organizer@test.test>
Attendees:
attendee1 <attendee1@example.com>
attendee2 <attendee2@test.test>
Summary : Test summury
Starts : Sat Aug 20 20:00:00 2022
End : Sat Aug 20 20:30:00 2022
Summary : Test summary
Starts : {secondstart}
End : {secondend}
Duration : 0:30:00
Location : New Amsterdam, 1010 Test Street
Comment :
@ -75,7 +89,7 @@ PROPER_OUTPUT = ''' Organizer: organizer <organizer@test.test>
Description:
'''
"""
class CLIToolTest(unittest.TestCase):
def test_output_is_proper(self):

Wyświetl plik

@ -0,0 +1,9 @@
'''Issue #165 - Problem parsing a file with event recurring on weekdays
https://github.com/collective/icalendar/issues/165
'''
from icalendar import Calendar
def test_issue_165_missing_event(calendars):
events = list(calendars.issue_165_missing_event.walk('VEVENT'))
assert len(events) == 1, "There was an event missing from the parsed events' list."

Wyświetl plik

@ -5,7 +5,10 @@
from icalendar import Calendar
def test_issue_27_multiple_periods(calendars):
free_busy = list(calendars.issue_27_multiple_periods.walk('VFREEBUSY'))
assert len(free_busy) == 1
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

Wyświetl plik

@ -0,0 +1,152 @@
"""These are tests for Issue #557
TL;DR: Component._encode lost given parameters
if the object to encode was already of native type,
making its behavior unexpected.
see https://github.com/collective/icalendar/issues/557"""
import unittest
from icalendar.cal import Component
class TestComponentEncode(unittest.TestCase):
def test_encode_non_native_parameters(self):
"""Test _encode to add parameters to non-natives"""
self.__assert_native_content(self.summary)
self.__assert_native_kept_parameters(self.summary)
def test_encode_native_keep_params_None(self):
"""_encode should keep parameters on natives
if parameters=None
"""
new_sum = self.__add_params(
self.summary,
parameters=None,
)
self.__assert_native_content(new_sum)
self.__assert_native_kept_parameters(new_sum)
def test_encode_native_keep_params_empty(self):
"""_encode should keep paramters on natives
if parameters={}
"""
new_sum = self.__add_params(
self.summary,
parameters={},
)
self.__assert_native_content(new_sum)
self.__assert_native_kept_parameters(new_sum)
def test_encode_native_append_params(self):
"""_encode should append paramters on natives
keeping old parameters
"""
new_sum = self.__add_params(
self.summary,
parameters={"X-PARAM": "Test123"},
)
self.__assert_native_content(new_sum)
self.__assert_native_kept_parameters(new_sum)
self.assertParameter(new_sum, "X-PARAM", "Test123")
def test_encode_native_overwrite_params(self):
"""_encode should overwrite single parameters
if they have the same name as old ones"""
new_sum = self.__add_params(
self.summary,
parameters={"LANGUAGE": "de"},
)
self.__assert_native_content(new_sum)
self.assertParameter(new_sum, "LANGUAGE", "de")
def test_encode_native_remove_params(self):
"""_encode should remove single parameters
if they are explicitly set to None"""
new_sum = self.__add_params(
self.summary,
parameters={"LANGUAGE": None},
)
self.__assert_native_content(new_sum)
self.assertParameterMissing(new_sum, "LANGUAGE")
def test_encode_native_remove_already_missing(self):
"""_encode should ignore removing a parameter
that was already missing"""
self.assertParameterMissing(self.summary, "X-MISSING")
new_sum = self.__add_params(
self.summary,
parameters={"X-MISSING": None},
)
self.__assert_native_content(new_sum)
self.__assert_native_kept_parameters(new_sum)
self.assertParameterMissing(self.summary, "X-MISSING")
def test_encode_native_full_test(self):
"""full test case with keeping, overwriting & removing properties"""
# preperation
orig_sum = self.__add_params(
self.summary,
parameters={
"X-OVERWRITE": "overwrite me!",
"X-REMOVE": "remove me!",
"X-MISSING": None,
},
)
# preperation check
self.__assert_native_content(orig_sum)
self.__assert_native_kept_parameters(orig_sum)
self.assertParameter(orig_sum, "X-OVERWRITE", "overwrite me!")
self.assertParameter(orig_sum, "X-REMOVE", "remove me!")
self.assertParameterMissing(orig_sum, "X-MISSING")
# modification
new_sum = self.__add_params(
orig_sum,
parameters={
"X-OVERWRITE": "overwritten",
"X-REMOVE": None,
"X-MISSING": None,
},
)
# final asserts
self.__assert_native_content(new_sum)
self.__assert_native_kept_parameters(new_sum)
self.assertParameter(new_sum, "X-OVERWRITE", "overwritten")
self.assertParameterMissing(new_sum, "X-REMOVE")
self.assertParameterMissing(new_sum, "X-MISSING")
def setUp(self):
self.summary = self.__gen_native()
def __assert_native_kept_parameters(self, obj):
self.assertParameter(obj, "LANGUAGE", "en")
def __assert_native_content(self, obj):
self.assertEqual(obj, "English Summary")
def __add_params(self, obj, parameters):
return Component._encode(
"SUMMARY",
obj,
parameters=parameters,
encode=True,
)
def __gen_native(self):
return Component._encode(
"SUMMARY",
"English Summary",
parameters={
"LANGUAGE": "en",
},
encode=True,
)
def assertParameterMissing(self, obj, name):
self.assertNotIn(name, obj.params)
def assertParameter(self, obj, name, val):
self.assertIn(name, obj.params)
self.assertEqual(obj.params[name], val)

Wyświetl plik

@ -7,6 +7,7 @@ See
import pytest
import pytz
from icalendar.prop import vDDDTypes
import datetime
@pytest.mark.parametrize("calname,tzname,index,period_string", [
@ -31,7 +32,7 @@ def test_issue_156_period_list_in_rdate(calendars, calname, tzname, index, perio
calendar = calendars[calname]
rdate = calendar.walk("vevent")[0]["rdate"]
period = rdate.dts[index]
assert period.dt == vDDDTypes.from_ical(period_string, timezone=pytz.timezone(tzname))
assert period.dt == vDDDTypes.from_ical(period_string, timezone=tzname)
def test_duration_properly_parsed(events):
@ -46,3 +47,17 @@ def test_duration_properly_parsed(events):
assert period[1].days == 0
assert period[1].seconds == (5 * 60 + 30) * 60
assert period[1] == duration
def test_tzid_is_part_of_the_parameters(calendars):
"""The TZID should be mentioned in the parameters."""
event = list(calendars.period_with_timezone.walk("VEVENT"))[0]
assert event["RDATE"].params["TZID"] == "America/Vancouver"
def test_tzid_is_part_of_the_period_values(calendars):
"""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 == pytz.timezone("America/Vancouver").localize(datetime.datetime(2023, 12, 13, 12))
assert end == pytz.timezone("America/Vancouver").localize(datetime.datetime(2023, 12, 13, 15))

Wyświetl plik

@ -1,7 +1,10 @@
import itertools
from datetime import datetime
from datetime import timedelta
import unittest
import pytest
import icalendar
import pytz
import re
@ -455,3 +458,33 @@ class TestCal(unittest.TestCase):
icalendar.vUTCOffset.ignore_exceptions = True
self.assertEqual(icalendar.Calendar.from_ical(cal_str).to_ical(), cal_str)
icalendar.vUTCOffset.ignore_exceptions = False
@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)
)
def test_comparing_calendars(calendars, calendar, other_calendar):
are_calendars_equal = calendars[calendar] == calendars[other_calendar]
are_calendars_actually_equal = calendar == other_calendar
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
assert calendars[calendar] == calendars[shuffeled_calendar]