Skip to content

Commit

Permalink
Merge pull request #565 from jacadzaca/issue-27
Browse files Browse the repository at this point in the history
Issue 27
  • Loading branch information
niccokunzmann authored Oct 2, 2023
2 parents 032a553 + 8fab650 commit 4cb7b12
Show file tree
Hide file tree
Showing 25 changed files with 555 additions and 50 deletions.
50 changes: 44 additions & 6 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -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:

Expand All @@ -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)
------------------
Expand Down
21 changes: 21 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------------------

Expand Down
6 changes: 2 additions & 4 deletions docs/about.rst
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 2 additions & 0 deletions docs/credits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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::

Expand Down
2 changes: 1 addition & 1 deletion docs/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
2 changes: 1 addition & 1 deletion docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion src/icalendar/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '5.0.7'
__version__ = '5.0.10'

from icalendar.cal import (
Calendar,
Expand Down
63 changes: 46 additions & 17 deletions src/icalendar/cal.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ def is_broken(self):
#############################
# 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.
Expand All @@ -138,17 +139,19 @@ def _encode(self, name, value, parameters=None, encode=1):
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):
Expand Down Expand Up @@ -372,19 +375,26 @@ def from_ical(cls, st, multiple=False):
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
Expand Down Expand Up @@ -436,6 +446,25 @@ def __repr__(self):
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
Expand Down
4 changes: 2 additions & 2 deletions src/icalendar/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
15 changes: 10 additions & 5 deletions src/icalendar/prop.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ def from_ical(cls, ical, timezone=None):
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)
Expand Down Expand Up @@ -533,6 +533,11 @@ def __cmp__(self, other):
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)
Expand All @@ -548,11 +553,11 @@ def to_ical(self):
+ 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}')
Expand Down Expand Up @@ -719,7 +724,7 @@ def __new__(cls, value, encoding=DEFAULT_ENCODING):
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)
Expand Down
40 changes: 40 additions & 0 deletions src/icalendar/tests/calendars/example.ics
Original file line number Diff line number Diff line change
@@ -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
28 changes: 28 additions & 0 deletions src/icalendar/tests/calendars/issue_165_missing_event.ics
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 4cb7b12

Please sign in to comment.