kopia lustrzana https://github.com/collective/icalendar
Merge branch 'master' into release-5.0.0
commit
12d4d38d26
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This template is there to guide you and help us. If you can not complete everything here, that is fine. -->
|
||||
## Describe the bug
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
## To Reproduce
|
||||
<!-- Please add the neccesary code here to reproduce the problem on your machine. -->
|
||||
|
||||
```
|
||||
import icalendar
|
||||
...
|
||||
```
|
||||
Output:
|
||||
<!-- If applicable, add logs or error outputs to help explain your problem. -->
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
## Expected behavior**
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
## Environment
|
||||
<!-- please complete the following information: -->
|
||||
- [ ] OS: <!-- e.g. Ubuntu 22 or Windows 10 -->
|
||||
- [ ] Python version: <!-- e.g. Python 3.10 -->
|
||||
- [ ] `icalendar` version: <!-- python3 -c 'import icalendar; print(icalendar.__version__)' -->
|
||||
|
||||
## Additional context
|
||||
<!-- Add any other context about the problem here, related issues and pull requests. -->
|
||||
- [ ] I tested it with the latest version `pip3 install https://github.com/collective/icalendar.git`
|
||||
- [ ] I attached the ICS source file or there is no ICS source file
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
name: empty issue
|
||||
about: This is an unstructured issue template to use with issues that are not described,
|
||||
yet.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This is an unstructured issue template to use if you do not need guidance.
|
||||
Here are some hints though:
|
||||
- add ICS example files if you can
|
||||
- show code if possible
|
||||
- show error output or output that differs from what you expect
|
||||
- it is nice to connect to the value of this issue
|
||||
Thanks for taking your time to report this!
|
||||
-->
|
|
@ -2,14 +2,17 @@ name: tests
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '14 7 * * 0' # run once a week on Sunday
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
run-tests:
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
|
@ -51,3 +54,61 @@ jobs:
|
|||
coveralls --service=github
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
deploy-tag-to-pypi:
|
||||
# only deploy on tags, see https://stackoverflow.com/a/58478262/1320237
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs:
|
||||
- run-tests
|
||||
runs-on: ubuntu-latest
|
||||
# This environment stores the TWINE_USERNAME and TWINE_PASSWORD
|
||||
# see https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment
|
||||
environment:
|
||||
name: PyPI
|
||||
url: https://pypi.org/project/icalendar/
|
||||
# after using the environment, we need to make the secrets available
|
||||
# see https://docs.github.com/en/actions/security-guides/encrypted-secrets#example-using-bash
|
||||
env:
|
||||
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
|
||||
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install wheel twine
|
||||
- name: Check the tag
|
||||
run: |
|
||||
PACKAGE_VERSION=`python setup.py --version`
|
||||
TAG_NAME=v$PACKAGE_VERSION
|
||||
echo "Package version $PACKAGE_VERSION with possible tag name $TAG_NAME on $GITHUB_REF_NAME"
|
||||
# test that the tag represents the version
|
||||
# see https://docs.github.com/en/actions/learn-github-actions/environment-variables
|
||||
if [ "$TAG_NAME" != "$GITHUB_REF_NAME" ]; then
|
||||
echo "ERROR: This tag is for the wrong version. Got \"$GITHUB_REF_NAME\" expected \"$TAG_NAME\"."
|
||||
exit 1
|
||||
fi
|
||||
- name: remove old files
|
||||
run: rm -rf dist/*
|
||||
- name: build distribution files
|
||||
run: python setup.py bdist_wheel sdist
|
||||
- name: deploy to pypi
|
||||
run: |
|
||||
# You will have to set the variables TWINE_USERNAME and TWINE_PASSWORD
|
||||
# You can use a token specific to your project by setting the user name to
|
||||
# __token__ and the password to the token given to you by the PyPI project.
|
||||
# sources:
|
||||
# - https://shambu2k.hashnode.dev/gitlab-to-pypi
|
||||
# - http://blog.octomy.org/2020/11/deploying-python-pacakges-to-pypi-using.html?m=1
|
||||
if [ -z "$TWINE_USERNAME" ]; then
|
||||
echo "WARNING: TWINE_USERNAME not set!"
|
||||
fi
|
||||
if [ -z "$TWINE_PASSWORD" ]; then
|
||||
echo "WARNING: TWINE_PASSWORD not set!"
|
||||
fi
|
||||
twine check dist/*
|
||||
twine upload dist/*
|
||||
|
|
|
@ -28,12 +28,15 @@ Minor changes:
|
|||
- removed deprecated test checks [tuergeist]
|
||||
- Fix: cli does not support DURATION #354 [mamico]
|
||||
- Add changelog and contributing to readthedocs documentation #428 [peleccom]
|
||||
- fixed small typos #323 [rohnsha0]
|
||||
- unittest to parametrized pytest refactoring [jacadzaca]
|
||||
|
||||
Breaking changes:
|
||||
|
||||
- Require Python 3.7 as minimum Python version. [maurits] [niccokunzmann]
|
||||
- icalenar now takes a ics file directly as an input
|
||||
- icalendar's output is different
|
||||
- Drop Support for Python 3.6. Versions 3.7 - 3.11 are supported and tested.
|
||||
|
||||
New features:
|
||||
|
||||
|
@ -53,6 +56,8 @@ Bug fixes:
|
|||
Ref: #338
|
||||
Fixes: #335
|
||||
[tobixen]
|
||||
- add ``__eq__`` to ``icalendar.prop.vDDDTypes`` #391 [jacadzaca]
|
||||
- Refactor deprecated unittest aliases for Python 3.11 compatibility #330 [tirkarthi]
|
||||
|
||||
5.0.0a1 (2022-07-11)
|
||||
--------------------
|
||||
|
|
1
setup.py
1
setup.py
|
@ -42,6 +42,7 @@ setuptools.setup(
|
|||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
],
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
BEGIN:VCALENDAR
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:An Event with too many semicolons
|
||||
DTSTART;;VALUE=DATE-TIME:20140409T093000
|
||||
UID:abc
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
|
@ -0,0 +1,23 @@
|
|||
BEGIN:VCALENDAR
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:(UTC-03:00) Brasília
|
||||
BEGIN:STANDARD
|
||||
TZNAME:Brasília standard
|
||||
DTSTART:16010101T235959
|
||||
TZOFFSETFROM:-0200
|
||||
TZOFFSETTO:-0300
|
||||
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=3SA;BYMONTH=2
|
||||
END:STANDARD
|
||||
BEGIN:DAYLIGHT
|
||||
TZNAME:Brasília daylight
|
||||
DTSTART:16010101T235959
|
||||
TZOFFSETFROM:-0300
|
||||
TZOFFSETTO:-0200
|
||||
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SA;BYMONTH=10
|
||||
END:DAYLIGHT
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
DTSTART;TZID="(UTC-03:00) Brasília":20170511T133000
|
||||
DTEND;TZID="(UTC-03:00) Brasília":20170511T140000
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
|
@ -1,5 +1,4 @@
|
|||
import os
|
||||
import logging
|
||||
import pytest
|
||||
import icalendar
|
||||
import pytz
|
||||
|
@ -10,7 +9,6 @@ try:
|
|||
except ModuleNotFoundError:
|
||||
from backports import zoneinfo
|
||||
|
||||
|
||||
class DataSource:
|
||||
'''A collection of parsed ICS elements (e.g calendars, timezones, events)'''
|
||||
def __init__(self, data_source_folder, parser):
|
||||
|
@ -24,7 +22,8 @@ class DataSource:
|
|||
with open(source_path, 'rb') as f:
|
||||
raw_ics = f.read()
|
||||
source = self._parser(raw_ics)
|
||||
source.raw_ics = raw_ics
|
||||
if not isinstance(source, list):
|
||||
source.raw_ics = raw_ics
|
||||
self.__dict__[attribute] = source
|
||||
return source
|
||||
|
||||
|
@ -34,10 +33,19 @@ class DataSource:
|
|||
def __repr__(self):
|
||||
return repr(self.__dict__)
|
||||
|
||||
@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))
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
CALENDARS_FOLDER = os.path.join(HERE, 'calendars')
|
||||
TIMEZONES_FOLDER = os.path.join(HERE, 'timezones')
|
||||
EVENTS_FOLDER = os.path.join(HERE, 'events')
|
||||
CALENDARS_FOLDER = os.path.join(HERE, 'calendars')
|
||||
|
||||
@pytest.fixture
|
||||
def calendars():
|
||||
return DataSource(CALENDARS_FOLDER, icalendar.Calendar.from_ical)
|
||||
|
||||
@pytest.fixture
|
||||
def timezones():
|
||||
|
@ -56,6 +64,10 @@ def events():
|
|||
def utc(request):
|
||||
return request.param
|
||||
|
||||
@pytest.fixture
|
||||
def calendars():
|
||||
return DataSource(CALENDARS_FOLDER, icalendar.Calendar.from_ical)
|
||||
@pytest.fixture(params=[
|
||||
lambda dt, tzname: pytz.timezone(tzname).localize(dt),
|
||||
lambda dt, tzname: dt.replace(tzinfo=tz.gettz(tzname)),
|
||||
lambda dt, tzname: dt.replace(tzinfo=zoneinfo.ZoneInfo(tzname))
|
||||
])
|
||||
def in_timezone(request):
|
||||
return request.param
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Meetup//RemoteApi//EN
|
||||
CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
X-ORIGINAL-URL:http://www.meetup.com/DevOpsDC/events/ical/DevOpsDC/
|
||||
X-WR-CALNAME:Events - DevOpsDC
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:America/New_York
|
||||
TZURL:http://tzurl.org/zoneinfo-outlook/America/New_York
|
||||
X-LIC-LOCATION:America/New_York
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:-0500
|
||||
TZOFFSETTO:-0400
|
||||
TZNAME:EDT
|
||||
DTSTART:19700308T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:-0400
|
||||
TZOFFSETTO:-0500
|
||||
TZNAME:EST
|
||||
DTSTART:19701101T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120605T003759Z
|
||||
DTSTART;TZID=America/New_York:20120712T183000
|
||||
DTEND;TZID=America/New_York:20120712T213000
|
||||
STATUS:CONFIRMED
|
||||
SUMMARY:DevOps DC Meetup
|
||||
DESCRIPTION:DevOpsDC\nThursday\, July 12 at 6:30 PM\n\nThis will be a joi
|
||||
nt meetup / hack night with the DC jQuery Users Group. The idea behind
|
||||
the hack night: Small teams consisting of at least 1 member...\n\nDeta
|
||||
ils: http://www.meetup.com/DevOpsDC/events/47635522/
|
||||
CLASS:PUBLIC
|
||||
CREATED:20120111T120339Z
|
||||
GEO:38.90;-77.01
|
||||
LOCATION:Fathom Creative\, Inc. (1333 14th Street Northwest\, Washington
|
||||
D.C.\, DC 20005)
|
||||
URL:http://www.meetup.com/DevOpsDC/events/47635522/
|
||||
LAST-MODIFIED:20120522T174406Z
|
||||
UID:event_qtkfrcyqkbnb@meetup.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120605T003759Z
|
||||
DTSTART;TZID=America/New_York:20120911T183000
|
||||
DTEND;TZID=America/New_York:20120911T213000
|
||||
STATUS:CONFIRMED
|
||||
SUMMARY:DevOps DC Meetup
|
||||
DESCRIPTION:DevOpsDC\nTuesday\, September 11 at 6:30 PM\n\n \n\nDetails:
|
||||
http://www.meetup.com/DevOpsDC/events/47635532/
|
||||
CLASS:PUBLIC
|
||||
CREATED:20120111T120352Z
|
||||
GEO:38.90;-77.01
|
||||
LOCATION:CustomInk\, LLC (7902 Westpark Drive\, McLean\, VA 22102)
|
||||
URL:http://www.meetup.com/DevOpsDC/events/47635532/
|
||||
LAST-MODIFIED:20120316T202210Z
|
||||
UID:event_qtkfrcyqmbpb@meetup.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120605T003759Z
|
||||
DTSTART;TZID=America/New_York:20121113T183000
|
||||
DTEND;TZID=America/New_York:20121113T213000
|
||||
STATUS:CONFIRMED
|
||||
SUMMARY:DevOps DC Meetup
|
||||
DESCRIPTION:DevOpsDC\nTuesday\, November 13 at 6:30 PM\n\n \n\nDetails: h
|
||||
ttp://www.meetup.com/DevOpsDC/events/47635552/
|
||||
CLASS:PUBLIC
|
||||
CREATED:20120111T120402Z
|
||||
GEO:38.90;-77.01
|
||||
LOCATION:CustomInk\, LLC (7902 Westpark Drive\, McLean\, VA 22102)
|
||||
URL:http://www.meetup.com/DevOpsDC/events/47635552/
|
||||
LAST-MODIFIED:20120316T202210Z
|
||||
UID:event_qtkfrcyqpbrb@meetup.com
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
|
@ -1,4 +1,5 @@
|
|||
import pytest
|
||||
import datetime
|
||||
|
||||
@pytest.mark.parametrize('field, expected_value', [
|
||||
('PRODID', '-//Plönë.org//NONSGML plone.app.event//EN'),
|
||||
|
@ -27,3 +28,41 @@ def test_events_parameter_unicoded(events):
|
|||
https://github.com/collective/icalendar/issues/101
|
||||
'''
|
||||
assert events.issue_101_icalendar_chokes_on_umlauts_in_organizer['ORGANIZER'].params['CN'] == 'acme, ädmin'
|
||||
|
||||
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')
|
||||
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'
|
||||
|
||||
@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}'
|
||||
|
||||
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.'
|
||||
error = event.errors[0][1]
|
||||
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'
|
||||
|
|
|
@ -42,47 +42,3 @@ class TestIssues(unittest.TestCase):
|
|||
event.to_ical(),
|
||||
icalendar.Event.from_ical(event.to_ical()).to_ical()
|
||||
)
|
||||
|
||||
def test_issue_237(self):
|
||||
"""Issue #237 - Fail to parse timezone with non-ascii TZID"""
|
||||
|
||||
ical_str = ['BEGIN:VCALENDAR',
|
||||
'BEGIN:VTIMEZONE',
|
||||
'TZID:(UTC-03:00) Brasília',
|
||||
'BEGIN:STANDARD',
|
||||
'TZNAME:Brasília standard',
|
||||
'DTSTART:16010101T235959',
|
||||
'TZOFFSETFROM:-0200',
|
||||
'TZOFFSETTO:-0300',
|
||||
'RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=3SA;BYMONTH=2',
|
||||
'END:STANDARD',
|
||||
'BEGIN:DAYLIGHT',
|
||||
'TZNAME:Brasília daylight',
|
||||
'DTSTART:16010101T235959',
|
||||
'TZOFFSETFROM:-0300',
|
||||
'TZOFFSETTO:-0200',
|
||||
'RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SA;BYMONTH=10',
|
||||
'END:DAYLIGHT',
|
||||
'END:VTIMEZONE',
|
||||
'BEGIN:VEVENT',
|
||||
'DTSTART;TZID=\"(UTC-03:00) Brasília\":20170511T133000',
|
||||
'DTEND;TZID=\"(UTC-03:00) Brasília\":20170511T140000',
|
||||
'END:VEVENT',
|
||||
'END:VCALENDAR',
|
||||
]
|
||||
|
||||
cal = icalendar.Calendar.from_ical('\r\n'.join(ical_str))
|
||||
self.assertEqual(cal.errors, [])
|
||||
|
||||
dtstart = cal.walk(name='VEVENT')[0].decoded("DTSTART")
|
||||
expected = pytz.timezone('America/Sao_Paulo').localize(datetime.datetime(2017, 5, 11, 13, 30))
|
||||
self.assertEqual(dtstart, expected)
|
||||
|
||||
try:
|
||||
expected_zone = '(UTC-03:00) Brasília'
|
||||
expected_tzname = 'Brasília standard'
|
||||
except UnicodeEncodeError:
|
||||
expected_zone = '(UTC-03:00) Brasília'.encode('ascii', 'replace')
|
||||
expected_tzname = 'Brasília standard'.encode('ascii', 'replace')
|
||||
self.assertEqual(dtstart.tzinfo.zone, expected_zone)
|
||||
self.assertEqual(dtstart.tzname(), expected_tzname)
|
||||
|
|
|
@ -291,34 +291,3 @@ class IcalendarTestCase (unittest.TestCase):
|
|||
'Max,Moller,"Rasmussen, Max"')
|
||||
|
||||
|
||||
class TestEncoding(unittest.TestCase):
|
||||
|
||||
def test_broken_property(self):
|
||||
"""
|
||||
Test if error messages are encode properly.
|
||||
"""
|
||||
broken_ical = textwrap.dedent("""
|
||||
BEGIN:VCALENDAR
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:An Event with too many semicolons
|
||||
DTSTART;;VALUE=DATE-TIME:20140409T093000
|
||||
UID:abc
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
""")
|
||||
cal = icalendar.Calendar.from_ical(broken_ical)
|
||||
for event in cal.walk('vevent'):
|
||||
self.assertEqual(len(event.errors), 1, 'Not the right amount of errors.')
|
||||
error = event.errors[0][1]
|
||||
self.assertTrue(error.startswith('Content line could not be parsed into parts'))
|
||||
|
||||
def test_apple_xlocation(self):
|
||||
"""
|
||||
Test if we support base64 encoded binary data in parameter values.
|
||||
"""
|
||||
directory = os.path.dirname(__file__)
|
||||
with open(os.path.join(directory, 'x_location.ics'), 'rb') as fp:
|
||||
data = fp.read()
|
||||
cal = icalendar.Calendar.from_ical(data)
|
||||
for event in cal.walk('vevent'):
|
||||
self.assertEqual(len(event.errors), 0, 'Got too many errors')
|
||||
|
|
|
@ -1,27 +1,15 @@
|
|||
"""An example with multiple VCALENDAR components"""
|
||||
from icalendar import Calendar
|
||||
from icalendar.prop import vText
|
||||
import unittest
|
||||
|
||||
import os
|
||||
|
||||
|
||||
class TestMultiple(unittest.TestCase):
|
||||
"""A example with multiple VCALENDAR components"""
|
||||
|
||||
def test_multiple(self):
|
||||
def test_multiple(calendars):
|
||||
"""Check opening multiple calendars."""
|
||||
|
||||
directory = os.path.dirname(__file__)
|
||||
with open(os.path.join(directory, 'multiple.ics'), 'rb') as fp:
|
||||
data = fp.read()
|
||||
cals = Calendar.from_ical(data, multiple=True)
|
||||
cals = calendars.multiple.multiple_calendar_components
|
||||
|
||||
self.assertEqual(len(cals), 2)
|
||||
self.assertSequenceEqual([comp.name for comp in cals[0].walk()],
|
||||
['VCALENDAR', 'VEVENT'])
|
||||
self.assertSequenceEqual([comp.name for comp in cals[1].walk()],
|
||||
['VCALENDAR', 'VEVENT', 'VEVENT'])
|
||||
|
||||
self.assertEqual(
|
||||
cals[0]['prodid'],
|
||||
vText('-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN')
|
||||
)
|
||||
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')
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
BEGIN:VTIMEZONE
|
||||
TZID:(UTC-03:00) Brasília
|
||||
BEGIN:STANDARD
|
||||
TZNAME:Brasília standard
|
||||
DTSTART:16010101T235959
|
||||
TZOFFSETFROM:-0200
|
||||
TZOFFSETTO:-0300
|
||||
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=3SA;BYMONTH=2
|
||||
END:STANDARD
|
||||
BEGIN:DAYLIGHT
|
||||
TZNAME:Brasília daylight
|
||||
DTSTART:16010101T235959
|
||||
TZOFFSETFROM:-0300
|
||||
TZOFFSETTO:-0200
|
||||
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SA;BYMONTH=10
|
||||
END:DAYLIGHT
|
||||
END:VTIMEZONE
|
Ładowanie…
Reference in New Issue