added yaml files for the first five meetings on the wiki page, along with yaml, ical, and setuptools python libraries
This commit is contained in:
parent
4ec020f743
commit
cf48d522b8
391
libs/icalendar-3.6.1/CHANGES.rst
Normal file
391
libs/icalendar-3.6.1/CHANGES.rst
Normal file
@ -0,0 +1,391 @@
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
3.6.1 (2014-01-13)
|
||||
------------------
|
||||
|
||||
- Open text files referenced by setup.py as utf-8, no matter what the locale
|
||||
settings are set to. Fixes #122.
|
||||
[sochotnicky]
|
||||
|
||||
- Add tox.ini to source tarball, which simplifies testing for in distributions.
|
||||
[sochotnicky]
|
||||
|
||||
|
||||
3.6 (2014-01-06)
|
||||
----------------
|
||||
|
||||
- Python3 (3.3+) + Python 2 (2.6+) support [geier]
|
||||
|
||||
- Made sure to_ical() always returns bytes [geier]
|
||||
|
||||
- Support adding lists to a component property, which value already was a list
|
||||
and remove the Component.set method, which was only used by the add method.
|
||||
[thet]
|
||||
|
||||
- Remove ability to add property parameters via a value's params attribute when
|
||||
adding via cal.add (that was only possible for custom value objects and makes
|
||||
up a strange API), but support a parameter attribute on cal.add's method
|
||||
signature to pass a dictionary with property parameter key/value pairs.
|
||||
Fixes #116.
|
||||
[thet]
|
||||
|
||||
- Backport some of Regebro's changes from his regebro-refactor branch.
|
||||
[thet]
|
||||
|
||||
- Raise explicit error on another malformed content line case.
|
||||
[hajdbo]
|
||||
|
||||
- Correctly parse datetime component property values with timezone information
|
||||
when parsed from ical strings.
|
||||
[untitaker]
|
||||
|
||||
|
||||
3.5 (2013-07-03)
|
||||
----------------
|
||||
|
||||
- Let to_unicode be more graceful for non-unicode strings, as like CMFPlone's
|
||||
safe_unicode does it.
|
||||
[thet]
|
||||
|
||||
|
||||
3.4 (2013-04-24)
|
||||
----------------
|
||||
|
||||
- Switch to unicode internally. This should fix all en/decoding errors.
|
||||
[thet]
|
||||
|
||||
- Support for non-ascii parameter values. Fixes #88.
|
||||
[warvariuc]
|
||||
|
||||
- Added functions to transform chars in string with '\\' + any of r'\,;:' chars
|
||||
into '%{:02X}' form to avoid splitting on chars escaped with '\\'.
|
||||
[warvariuc]
|
||||
|
||||
- Allow seconds in vUTCOffset properties. Fixes #55.
|
||||
[thet]
|
||||
|
||||
- Let ``Component.decode`` better handle vRecur and vDDDLists properties.
|
||||
Fixes #70.
|
||||
[thet]
|
||||
|
||||
- Don't let ``Component.add`` re-encode already encoded values. This simplifies
|
||||
the API, since there is no need explicitly pass ``encode=False``. Fixes #82.
|
||||
[thet]
|
||||
|
||||
- Rename tzinfo_from_dt to tzid_from_dt, which is what it does.
|
||||
[thet]
|
||||
|
||||
- More support for dateutil parsed tzinfo objects. Fixes #89.
|
||||
[leo-naeka]
|
||||
|
||||
- Remove python-dateutil version fix at all. Current python-dateutil has Py3
|
||||
and Py2 compatibility.
|
||||
[thet]
|
||||
|
||||
- Declare the required python-dateutil dependency in setup.py. Fixes #90.
|
||||
[kleink]
|
||||
|
||||
- Raise test coverage.
|
||||
[thet]
|
||||
|
||||
- Remove interfaces module, as it is unused.
|
||||
[thet]
|
||||
|
||||
- Remove ``test_doctests.py``, test suite already created properly in
|
||||
``test_icalendar.py``.
|
||||
[rnix]
|
||||
|
||||
- Transformed doctests into unittests, Test fixes and cleanup.
|
||||
[warvariuc]
|
||||
|
||||
|
||||
3.3 (2013-02-08)
|
||||
----------------
|
||||
|
||||
- Drop support for Python < 2.6.
|
||||
[thet]
|
||||
|
||||
- Allow vGeo to be instantiated with list and not only tuples of geo
|
||||
coordinates. Fixes #83.
|
||||
[thet]
|
||||
|
||||
- Don't force to pass a list to vDDDLists and allow setting individual RDATE
|
||||
and EXDATE values without having to wrap them in a list.
|
||||
[thet]
|
||||
|
||||
- Fix encoding function to allow setting RDATE and EXDATE values and not to
|
||||
have bypass encoding with an icalendar property.
|
||||
[thet]
|
||||
|
||||
- Allow setting of timezone for vDDDLists and support timezone properties for
|
||||
RDATE and EXDATE component properties.
|
||||
[thet]
|
||||
|
||||
- Move setting of TZID properties to vDDDTypes, where it belongs to.
|
||||
[thet]
|
||||
|
||||
- Use @staticmethod decorator instead of wrapper function.
|
||||
[warvariuc, thet]
|
||||
|
||||
- Extend quoting of parameter values to all of those characters: ",;: ’'".
|
||||
This fixes an outlook incompatibility with some characters. Fixes: #79,
|
||||
Fixes: #81.
|
||||
[warvariuc]
|
||||
|
||||
- Define VTIMETZONE subcomponents STANDARD and DAYLIGHT for RFC5545 compliance.
|
||||
[thet]
|
||||
|
||||
|
||||
3.2 (2012-11-27)
|
||||
----------------
|
||||
|
||||
- Documentation file layout restructuring.
|
||||
[thet]
|
||||
|
||||
- Fix time support. vTime events can be instantiated with a datetime.time
|
||||
object, and do not inherit from datetime.time itself.
|
||||
[rdunklau]
|
||||
|
||||
- Correctly handle tzinfo objects parsed with dateutil. Fixes #77.
|
||||
[warvariuc, thet]
|
||||
|
||||
- Text values are escaped correclty. Fixes #74.
|
||||
[warvariuc]
|
||||
|
||||
- Returned old folding algorithm, as the current implementation fails in some
|
||||
cases. Fixes #72, Fixes #73.
|
||||
[warvariuc]
|
||||
|
||||
- Supports to_ical() on date/time properties for dates prior to 1900.
|
||||
[cdevienne]
|
||||
|
||||
|
||||
3.1 (2012-09-05)
|
||||
----------------
|
||||
|
||||
- Make sure parameters to certain properties propagate to the ical output.
|
||||
[kanarip]
|
||||
|
||||
- Re-include doctests.
|
||||
[rnix]
|
||||
|
||||
- Ensure correct datatype at instance creation time in ``prop.vCalAddress``
|
||||
and ``prop.vText``.
|
||||
[rnix]
|
||||
|
||||
- Apply TZID parameter to datetimes parsed from RECURRENCE-ID
|
||||
[dbstovall]
|
||||
|
||||
- Localize datetimes for timezones to avoid DST transition errors.
|
||||
[dbstovall]
|
||||
|
||||
- Allow UTC-OFFSET property value data types in seconds, which follows RFC5545
|
||||
specification.
|
||||
[nikolaeff]
|
||||
|
||||
- Remove utctz and normalized_timezone methods to simplify the codebase. The
|
||||
methods were too tiny to be useful and just used at one place.
|
||||
[thet]
|
||||
|
||||
- When using Component.add() to add icalendar properties, force a value
|
||||
conversion to UTC for CREATED, DTSTART and LAST-MODIFIED. The RFC expects UTC
|
||||
for those properties.
|
||||
[thet]
|
||||
|
||||
- Removed last occurrences of old API (from_string).
|
||||
[Rembane]
|
||||
|
||||
- Add 'recursive' argument to property_items() to switch recursive listing.
|
||||
For example when parsing a text/calendar text including multiple components
|
||||
(e.g. a VCALENDAR with 5 VEVENTs), the previous situation required us to look
|
||||
over all properties in VEVENTs even if we just want the properties under the
|
||||
VCALENDAR component (VERSION, PRODID, CALSCALE, METHOD).
|
||||
[dmikurube]
|
||||
|
||||
- All unit tests fixed.
|
||||
[mikaelfrykholm]
|
||||
|
||||
|
||||
3.0.1b2 (2012-03-01)
|
||||
--------------------
|
||||
|
||||
- For all TZID parameters in DATE-TIME properties, use timezone identifiers
|
||||
(e.g. Europe/Vienna) instead of timezone names (e.g. CET), as required by
|
||||
RFC5545. Timezone names are used together with timezone identifiers in the
|
||||
Timezone components.
|
||||
[thet]
|
||||
|
||||
- Timezone parsing, issues and test fixes.
|
||||
[mikaelfrykholm, garbas, tgecho]
|
||||
|
||||
- Since we use pytz for timezones, also use UTC tzinfo object from the pytz
|
||||
library instead of own implementation.
|
||||
[thet]
|
||||
|
||||
|
||||
3.0.1b1 (2012-02-24)
|
||||
--------------------
|
||||
|
||||
- Update Release information.
|
||||
[thet]
|
||||
|
||||
|
||||
3.0
|
||||
---
|
||||
|
||||
- Add API for proper Timezone support. Allow creating ical DATE-TIME strings
|
||||
with timezone information from Python datetimes with pytz based timezone
|
||||
information and vice versa.
|
||||
[thet]
|
||||
|
||||
- Unify API to only use to_ical and from_ical and remove string casting as a
|
||||
requirement for Python 3 compatibility:
|
||||
New: to_ical.
|
||||
Old: ical, string, as_string and string casting via __str__ and str.
|
||||
New: from_ical.
|
||||
Old: from_string.
|
||||
[thet]
|
||||
|
||||
|
||||
2.2 (2011-08-24)
|
||||
----------------
|
||||
|
||||
- migration to https://github.com/collective/icalendar using svn2git preserving
|
||||
tags, branches and authors.
|
||||
[garbas]
|
||||
|
||||
- using tox for testing on python 2.4, 2.5, 2.6, 2.6.
|
||||
[garbas]
|
||||
|
||||
- fixed tests so they pass also under python 2.7.
|
||||
[garbas]
|
||||
|
||||
- running tests on https://jenkins.plone.org/job/icalendar (only 2.6 for now)
|
||||
with some other metrics (pylint, clonedigger, coverage).
|
||||
[garbas]
|
||||
|
||||
- review and merge changes from https://github.com/cozi/icalendar fork.
|
||||
[garbas]
|
||||
|
||||
- created sphinx documentation and started documenting development and goals.
|
||||
[garbas]
|
||||
|
||||
- hook out github repository to http://readthedocs.org service so sphinx
|
||||
documentation is generated on each commit (for master). Documentation can be
|
||||
visible on: http://readthedocs.org/docs/icalendar/en/latest/
|
||||
[garbas]
|
||||
|
||||
|
||||
2.1 (2009-12-14)
|
||||
----------------
|
||||
|
||||
- Fix deprecation warnings about ``object.__init__`` taking no parameters.
|
||||
|
||||
- Set the VALUE parameter correctly for date values.
|
||||
|
||||
- Long binary data would be base64 encoded with newlines, which made the
|
||||
iCalendar files incorrect. (This still needs testing).
|
||||
|
||||
- Correctly handle content lines which include newlines.
|
||||
|
||||
|
||||
2.0.1 (2008-07-11)
|
||||
------------------
|
||||
|
||||
- Made the tests run under Python 2.5+
|
||||
|
||||
- Renamed the UTC class to Utc, so it would not clash with the UTC object,
|
||||
since that rendered the UTC object unpicklable.
|
||||
|
||||
|
||||
2.0 (2008-07-11)
|
||||
----------------
|
||||
|
||||
- EXDATE and RDATE now returns a vDDDLists object, which contains a list
|
||||
of vDDDTypes objects. This is do that EXDATE and RDATE can contain
|
||||
lists of dates, as per RFC.
|
||||
|
||||
***Note!***: This change is incompatible with earlier behavior, so if you
|
||||
handle EXDATE and RDATE you will need to update your code.
|
||||
|
||||
- When createing a vDuration of -5 hours (which in itself is nonsensical),
|
||||
the ical output of that was -P1DT19H, which is correct, but ugly. Now
|
||||
it's '-PT5H', which is prettier.
|
||||
|
||||
|
||||
1.2 (2006-11-25)
|
||||
----------------
|
||||
|
||||
- Fixed a string index out of range error in the new folding code.
|
||||
|
||||
|
||||
1.1 (2006-11-23)
|
||||
----------------
|
||||
|
||||
- Fixed a bug in caselessdicts popitem. (thanks to Michael Smith
|
||||
<msmith@fluendo.com>)
|
||||
|
||||
- The RFC 2445 was a bit unclear on how to handle line folding when it
|
||||
happened to be in the middle of a UTF-8 character. This has been clarified
|
||||
in the following discussion:
|
||||
http://lists.osafoundation.org/pipermail/ietf-calsify/2006-August/001126.html
|
||||
And this is now implemented in iCalendar. It will not fold in the middle of
|
||||
a UTF-8 character, but may fold in the middle of a UTF-8 composing character
|
||||
sequence.
|
||||
|
||||
|
||||
1.0 (2006-08-03)
|
||||
----------------
|
||||
|
||||
- make get_inline and set_inline support non ascii codes.
|
||||
|
||||
- Added support for creating a python egg distribution.
|
||||
|
||||
|
||||
0.11 (2005-11-08)
|
||||
-----------------
|
||||
|
||||
- Changed component .from_string to use types_factory instead of hardcoding
|
||||
entries to 'inline'
|
||||
|
||||
- Changed UTC tzinfo to a singleton so the same one is used everywhere
|
||||
|
||||
- Made the parser more strict by using regular expressions for key name,
|
||||
param name and quoted/unquoted safe char as per the RFC
|
||||
|
||||
- Added some tests from the schooltool icalendar parser for better coverage
|
||||
|
||||
- Be more forgiving on the regex for folding lines
|
||||
|
||||
- Allow for multiple top-level components on .from_string
|
||||
|
||||
- Fix vWeekdays, wasn't accepting relative param (eg: -3SA vs -SA)
|
||||
|
||||
- vDDDTypes didn't accept negative period (eg: -P30M)
|
||||
|
||||
- 'N' is also acceptable as newline on content lines, per RFC
|
||||
|
||||
|
||||
0.10 (2005-04-28)
|
||||
-----------------
|
||||
|
||||
- moved code to codespeak.net subversion.
|
||||
|
||||
- reorganized package structure so that source code is under 'src' directory.
|
||||
Non-package files remain in distribution root.
|
||||
|
||||
- redid doc/.py files as doc/.txt, using more modern doctest. Before they
|
||||
were .py files with big docstrings.
|
||||
|
||||
- added test.py testrunner, and tests/test_icalendar.py that picks up all
|
||||
doctests in source code and doc directory, and runs them, when typing::
|
||||
|
||||
python2.3 test.py
|
||||
|
||||
- renamed iCalendar to lower case package name, lowercased, de-pluralized and
|
||||
shorted module names, which are mostly implementation detail.
|
||||
|
||||
- changed tests so they generate .ics files in a temp directory, not in the
|
||||
structure itself.
|
18
libs/icalendar-3.6.1/CONTRIBUTING.rst
Normal file
18
libs/icalendar-3.6.1/CONTRIBUTING.rst
Normal file
@ -0,0 +1,18 @@
|
||||
You want to help and contribute? Perfect!
|
||||
=========================================
|
||||
|
||||
These are some contribution examples
|
||||
------------------------------------
|
||||
|
||||
* Reporting issues to the bugtracker.
|
||||
* Submitting pull requests from a forked icalendar repo.
|
||||
* Extending the documentation.
|
||||
* Sponsor a Sprint (http://plone.org/events/sprints/whatis).
|
||||
|
||||
|
||||
For pull requests, keep this in mind
|
||||
------------------------------------
|
||||
|
||||
* Add a test which proves your fix and make it pass.
|
||||
* Describe your change in CHANGES.rst
|
||||
* Add yourself to the docs/credits.rst
|
27
libs/icalendar-3.6.1/LICENSE.rst
Normal file
27
libs/icalendar-3.6.1/LICENSE.rst
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
Copyright (c) 2012-2013, Plone Foundation
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
4
libs/icalendar-3.6.1/MANIFEST.in
Normal file
4
libs/icalendar-3.6.1/MANIFEST.in
Normal file
@ -0,0 +1,4 @@
|
||||
include *.rst tox.ini
|
||||
graft docs
|
||||
recursive-include src/icalendar *
|
||||
recursive-exclude src/icalendar *.pyc
|
534
libs/icalendar-3.6.1/PKG-INFO
Normal file
534
libs/icalendar-3.6.1/PKG-INFO
Normal file
@ -0,0 +1,534 @@
|
||||
Metadata-Version: 1.1
|
||||
Name: icalendar
|
||||
Version: 3.6.1
|
||||
Summary: iCalendar parser/generator
|
||||
Home-page: https://github.com/collective/icalendar
|
||||
Author: Plone Foundation
|
||||
Author-email: plone-developers@lists.sourceforge.net
|
||||
License: BSD
|
||||
Description: ==========================================================
|
||||
Internet Calendaring and Scheduling (iCalendar) for Python
|
||||
==========================================================
|
||||
|
||||
The `icalendar`_ package is a parser/generator of iCalendar files for use
|
||||
with Python.
|
||||
|
||||
----
|
||||
|
||||
:Homepage: http://icalendar.readthedocs.org
|
||||
:Code: http://github.com/collective/icalendar
|
||||
:Mailing list: http://github.com/collective/icalendar/issues
|
||||
:Dependencies: `setuptools`_ and since version 3.0 we depend on `pytz`_.
|
||||
:Compatible with: Python 2.6, 2.7 and 3.3+
|
||||
:License: `BSD`_
|
||||
|
||||
----
|
||||
|
||||
|
||||
Roadmap
|
||||
=======
|
||||
|
||||
- 3.6: Python 3 support (current version)
|
||||
|
||||
- 4.0: API refactoring
|
||||
|
||||
|
||||
|
||||
Changes in version 3.0
|
||||
======================
|
||||
|
||||
API Change
|
||||
----------
|
||||
|
||||
Since version we unified to icalendar de/serialization API to use only to_ical
|
||||
(for writing an ical string from the internal representation) and from_ical
|
||||
(for parsing an ical string into the internal representation).
|
||||
|
||||
to_ical is now used instead of the methods ical, string, as_string and instead
|
||||
of string casting via __str__ and str.
|
||||
|
||||
from_ical is now used instead of from_string.
|
||||
|
||||
This change is a requirement for future Python 3 compatibility. Please update
|
||||
your code to reflect to the new API.
|
||||
|
||||
Timezone support
|
||||
----------------
|
||||
|
||||
Timezones are now fully supported in icalendar for serialization and
|
||||
deserialization. We use the pytz library for timezone components of datetime
|
||||
instances. The timezone identifiers must be valid pytz respectively Olson
|
||||
database timezone identifiers. This can be a problem for 'GMT' identifiers,
|
||||
which are not defined in the Olson database.
|
||||
|
||||
Instead of the own UTC tzinfo implementation we use pytz UTC tzinfo object now.
|
||||
|
||||
|
||||
About this fork which is not a fork anymore
|
||||
===========================================
|
||||
|
||||
Aim of this fork (not fork anymore, read further) was to bring this package up
|
||||
to date with latest icalendar `RFC`_ specification as part of
|
||||
`plone.app.event`_ project which goal is to bring recurrent evens to `Plone`_.
|
||||
|
||||
After some thoughts we (Plone developers involved with `plone.app.event`_) send
|
||||
a suggestion to icalendar-dev@codespeak.net to take over mainaining of
|
||||
`icalendar`_. Nobody object and since version 2.2 we are back to development.
|
||||
|
||||
.. _`icalendar`: http://pypi.python.org/pypi/icalendar
|
||||
.. _`plone.app.event`: http://github.com/plone/plone.app.event
|
||||
.. _`Plone`: http://plone.org
|
||||
.. _`pytz`: http://pypi.python.org/pypi/pytz
|
||||
.. _`setuptools`: http://pypi.python.org/pypi/setuptools
|
||||
.. _`RFC`: http://www.ietf.org/rfc/rfc5545.txt
|
||||
.. _`BSD`: https://github.com/collective/icalendar/issues/2
|
||||
|
||||
|
||||
Test Coverage Report
|
||||
====================
|
||||
|
||||
Output from coverage test::
|
||||
|
||||
Name Stmts Miss Cover
|
||||
----------------------------------------------------------------------------------
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/__init__ 5 0 100%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/cal 234 7 97%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/caselessdict 55 5 91%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/compat 1 0 100%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/parser 189 6 97%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/parser_tools 20 0 100%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/prop 533 62 88%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/tools 16 0 100%
|
||||
----------------------------------------------------------------------------------
|
||||
TOTAL 1053 80 92%
|
||||
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
3.6.1 (2014-01-13)
|
||||
------------------
|
||||
|
||||
- Open text files referenced by setup.py as utf-8, no matter what the locale
|
||||
settings are set to. Fixes #122.
|
||||
[sochotnicky]
|
||||
|
||||
- Add tox.ini to source tarball, which simplifies testing for in distributions.
|
||||
[sochotnicky]
|
||||
|
||||
|
||||
3.6 (2014-01-06)
|
||||
----------------
|
||||
|
||||
- Python3 (3.3+) + Python 2 (2.6+) support [geier]
|
||||
|
||||
- Made sure to_ical() always returns bytes [geier]
|
||||
|
||||
- Support adding lists to a component property, which value already was a list
|
||||
and remove the Component.set method, which was only used by the add method.
|
||||
[thet]
|
||||
|
||||
- Remove ability to add property parameters via a value's params attribute when
|
||||
adding via cal.add (that was only possible for custom value objects and makes
|
||||
up a strange API), but support a parameter attribute on cal.add's method
|
||||
signature to pass a dictionary with property parameter key/value pairs.
|
||||
Fixes #116.
|
||||
[thet]
|
||||
|
||||
- Backport some of Regebro's changes from his regebro-refactor branch.
|
||||
[thet]
|
||||
|
||||
- Raise explicit error on another malformed content line case.
|
||||
[hajdbo]
|
||||
|
||||
- Correctly parse datetime component property values with timezone information
|
||||
when parsed from ical strings.
|
||||
[untitaker]
|
||||
|
||||
|
||||
3.5 (2013-07-03)
|
||||
----------------
|
||||
|
||||
- Let to_unicode be more graceful for non-unicode strings, as like CMFPlone's
|
||||
safe_unicode does it.
|
||||
[thet]
|
||||
|
||||
|
||||
3.4 (2013-04-24)
|
||||
----------------
|
||||
|
||||
- Switch to unicode internally. This should fix all en/decoding errors.
|
||||
[thet]
|
||||
|
||||
- Support for non-ascii parameter values. Fixes #88.
|
||||
[warvariuc]
|
||||
|
||||
- Added functions to transform chars in string with '\\' + any of r'\,;:' chars
|
||||
into '%{:02X}' form to avoid splitting on chars escaped with '\\'.
|
||||
[warvariuc]
|
||||
|
||||
- Allow seconds in vUTCOffset properties. Fixes #55.
|
||||
[thet]
|
||||
|
||||
- Let ``Component.decode`` better handle vRecur and vDDDLists properties.
|
||||
Fixes #70.
|
||||
[thet]
|
||||
|
||||
- Don't let ``Component.add`` re-encode already encoded values. This simplifies
|
||||
the API, since there is no need explicitly pass ``encode=False``. Fixes #82.
|
||||
[thet]
|
||||
|
||||
- Rename tzinfo_from_dt to tzid_from_dt, which is what it does.
|
||||
[thet]
|
||||
|
||||
- More support for dateutil parsed tzinfo objects. Fixes #89.
|
||||
[leo-naeka]
|
||||
|
||||
- Remove python-dateutil version fix at all. Current python-dateutil has Py3
|
||||
and Py2 compatibility.
|
||||
[thet]
|
||||
|
||||
- Declare the required python-dateutil dependency in setup.py. Fixes #90.
|
||||
[kleink]
|
||||
|
||||
- Raise test coverage.
|
||||
[thet]
|
||||
|
||||
- Remove interfaces module, as it is unused.
|
||||
[thet]
|
||||
|
||||
- Remove ``test_doctests.py``, test suite already created properly in
|
||||
``test_icalendar.py``.
|
||||
[rnix]
|
||||
|
||||
- Transformed doctests into unittests, Test fixes and cleanup.
|
||||
[warvariuc]
|
||||
|
||||
|
||||
3.3 (2013-02-08)
|
||||
----------------
|
||||
|
||||
- Drop support for Python < 2.6.
|
||||
[thet]
|
||||
|
||||
- Allow vGeo to be instantiated with list and not only tuples of geo
|
||||
coordinates. Fixes #83.
|
||||
[thet]
|
||||
|
||||
- Don't force to pass a list to vDDDLists and allow setting individual RDATE
|
||||
and EXDATE values without having to wrap them in a list.
|
||||
[thet]
|
||||
|
||||
- Fix encoding function to allow setting RDATE and EXDATE values and not to
|
||||
have bypass encoding with an icalendar property.
|
||||
[thet]
|
||||
|
||||
- Allow setting of timezone for vDDDLists and support timezone properties for
|
||||
RDATE and EXDATE component properties.
|
||||
[thet]
|
||||
|
||||
- Move setting of TZID properties to vDDDTypes, where it belongs to.
|
||||
[thet]
|
||||
|
||||
- Use @staticmethod decorator instead of wrapper function.
|
||||
[warvariuc, thet]
|
||||
|
||||
- Extend quoting of parameter values to all of those characters: ",;: ’'".
|
||||
This fixes an outlook incompatibility with some characters. Fixes: #79,
|
||||
Fixes: #81.
|
||||
[warvariuc]
|
||||
|
||||
- Define VTIMETZONE subcomponents STANDARD and DAYLIGHT for RFC5545 compliance.
|
||||
[thet]
|
||||
|
||||
|
||||
3.2 (2012-11-27)
|
||||
----------------
|
||||
|
||||
- Documentation file layout restructuring.
|
||||
[thet]
|
||||
|
||||
- Fix time support. vTime events can be instantiated with a datetime.time
|
||||
object, and do not inherit from datetime.time itself.
|
||||
[rdunklau]
|
||||
|
||||
- Correctly handle tzinfo objects parsed with dateutil. Fixes #77.
|
||||
[warvariuc, thet]
|
||||
|
||||
- Text values are escaped correclty. Fixes #74.
|
||||
[warvariuc]
|
||||
|
||||
- Returned old folding algorithm, as the current implementation fails in some
|
||||
cases. Fixes #72, Fixes #73.
|
||||
[warvariuc]
|
||||
|
||||
- Supports to_ical() on date/time properties for dates prior to 1900.
|
||||
[cdevienne]
|
||||
|
||||
|
||||
3.1 (2012-09-05)
|
||||
----------------
|
||||
|
||||
- Make sure parameters to certain properties propagate to the ical output.
|
||||
[kanarip]
|
||||
|
||||
- Re-include doctests.
|
||||
[rnix]
|
||||
|
||||
- Ensure correct datatype at instance creation time in ``prop.vCalAddress``
|
||||
and ``prop.vText``.
|
||||
[rnix]
|
||||
|
||||
- Apply TZID parameter to datetimes parsed from RECURRENCE-ID
|
||||
[dbstovall]
|
||||
|
||||
- Localize datetimes for timezones to avoid DST transition errors.
|
||||
[dbstovall]
|
||||
|
||||
- Allow UTC-OFFSET property value data types in seconds, which follows RFC5545
|
||||
specification.
|
||||
[nikolaeff]
|
||||
|
||||
- Remove utctz and normalized_timezone methods to simplify the codebase. The
|
||||
methods were too tiny to be useful and just used at one place.
|
||||
[thet]
|
||||
|
||||
- When using Component.add() to add icalendar properties, force a value
|
||||
conversion to UTC for CREATED, DTSTART and LAST-MODIFIED. The RFC expects UTC
|
||||
for those properties.
|
||||
[thet]
|
||||
|
||||
- Removed last occurrences of old API (from_string).
|
||||
[Rembane]
|
||||
|
||||
- Add 'recursive' argument to property_items() to switch recursive listing.
|
||||
For example when parsing a text/calendar text including multiple components
|
||||
(e.g. a VCALENDAR with 5 VEVENTs), the previous situation required us to look
|
||||
over all properties in VEVENTs even if we just want the properties under the
|
||||
VCALENDAR component (VERSION, PRODID, CALSCALE, METHOD).
|
||||
[dmikurube]
|
||||
|
||||
- All unit tests fixed.
|
||||
[mikaelfrykholm]
|
||||
|
||||
|
||||
3.0.1b2 (2012-03-01)
|
||||
--------------------
|
||||
|
||||
- For all TZID parameters in DATE-TIME properties, use timezone identifiers
|
||||
(e.g. Europe/Vienna) instead of timezone names (e.g. CET), as required by
|
||||
RFC5545. Timezone names are used together with timezone identifiers in the
|
||||
Timezone components.
|
||||
[thet]
|
||||
|
||||
- Timezone parsing, issues and test fixes.
|
||||
[mikaelfrykholm, garbas, tgecho]
|
||||
|
||||
- Since we use pytz for timezones, also use UTC tzinfo object from the pytz
|
||||
library instead of own implementation.
|
||||
[thet]
|
||||
|
||||
|
||||
3.0.1b1 (2012-02-24)
|
||||
--------------------
|
||||
|
||||
- Update Release information.
|
||||
[thet]
|
||||
|
||||
|
||||
3.0
|
||||
---
|
||||
|
||||
- Add API for proper Timezone support. Allow creating ical DATE-TIME strings
|
||||
with timezone information from Python datetimes with pytz based timezone
|
||||
information and vice versa.
|
||||
[thet]
|
||||
|
||||
- Unify API to only use to_ical and from_ical and remove string casting as a
|
||||
requirement for Python 3 compatibility:
|
||||
New: to_ical.
|
||||
Old: ical, string, as_string and string casting via __str__ and str.
|
||||
New: from_ical.
|
||||
Old: from_string.
|
||||
[thet]
|
||||
|
||||
|
||||
2.2 (2011-08-24)
|
||||
----------------
|
||||
|
||||
- migration to https://github.com/collective/icalendar using svn2git preserving
|
||||
tags, branches and authors.
|
||||
[garbas]
|
||||
|
||||
- using tox for testing on python 2.4, 2.5, 2.6, 2.6.
|
||||
[garbas]
|
||||
|
||||
- fixed tests so they pass also under python 2.7.
|
||||
[garbas]
|
||||
|
||||
- running tests on https://jenkins.plone.org/job/icalendar (only 2.6 for now)
|
||||
with some other metrics (pylint, clonedigger, coverage).
|
||||
[garbas]
|
||||
|
||||
- review and merge changes from https://github.com/cozi/icalendar fork.
|
||||
[garbas]
|
||||
|
||||
- created sphinx documentation and started documenting development and goals.
|
||||
[garbas]
|
||||
|
||||
- hook out github repository to http://readthedocs.org service so sphinx
|
||||
documentation is generated on each commit (for master). Documentation can be
|
||||
visible on: http://readthedocs.org/docs/icalendar/en/latest/
|
||||
[garbas]
|
||||
|
||||
|
||||
2.1 (2009-12-14)
|
||||
----------------
|
||||
|
||||
- Fix deprecation warnings about ``object.__init__`` taking no parameters.
|
||||
|
||||
- Set the VALUE parameter correctly for date values.
|
||||
|
||||
- Long binary data would be base64 encoded with newlines, which made the
|
||||
iCalendar files incorrect. (This still needs testing).
|
||||
|
||||
- Correctly handle content lines which include newlines.
|
||||
|
||||
|
||||
2.0.1 (2008-07-11)
|
||||
------------------
|
||||
|
||||
- Made the tests run under Python 2.5+
|
||||
|
||||
- Renamed the UTC class to Utc, so it would not clash with the UTC object,
|
||||
since that rendered the UTC object unpicklable.
|
||||
|
||||
|
||||
2.0 (2008-07-11)
|
||||
----------------
|
||||
|
||||
- EXDATE and RDATE now returns a vDDDLists object, which contains a list
|
||||
of vDDDTypes objects. This is do that EXDATE and RDATE can contain
|
||||
lists of dates, as per RFC.
|
||||
|
||||
***Note!***: This change is incompatible with earlier behavior, so if you
|
||||
handle EXDATE and RDATE you will need to update your code.
|
||||
|
||||
- When createing a vDuration of -5 hours (which in itself is nonsensical),
|
||||
the ical output of that was -P1DT19H, which is correct, but ugly. Now
|
||||
it's '-PT5H', which is prettier.
|
||||
|
||||
|
||||
1.2 (2006-11-25)
|
||||
----------------
|
||||
|
||||
- Fixed a string index out of range error in the new folding code.
|
||||
|
||||
|
||||
1.1 (2006-11-23)
|
||||
----------------
|
||||
|
||||
- Fixed a bug in caselessdicts popitem. (thanks to Michael Smith
|
||||
<msmith@fluendo.com>)
|
||||
|
||||
- The RFC 2445 was a bit unclear on how to handle line folding when it
|
||||
happened to be in the middle of a UTF-8 character. This has been clarified
|
||||
in the following discussion:
|
||||
http://lists.osafoundation.org/pipermail/ietf-calsify/2006-August/001126.html
|
||||
And this is now implemented in iCalendar. It will not fold in the middle of
|
||||
a UTF-8 character, but may fold in the middle of a UTF-8 composing character
|
||||
sequence.
|
||||
|
||||
|
||||
1.0 (2006-08-03)
|
||||
----------------
|
||||
|
||||
- make get_inline and set_inline support non ascii codes.
|
||||
|
||||
- Added support for creating a python egg distribution.
|
||||
|
||||
|
||||
0.11 (2005-11-08)
|
||||
-----------------
|
||||
|
||||
- Changed component .from_string to use types_factory instead of hardcoding
|
||||
entries to 'inline'
|
||||
|
||||
- Changed UTC tzinfo to a singleton so the same one is used everywhere
|
||||
|
||||
- Made the parser more strict by using regular expressions for key name,
|
||||
param name and quoted/unquoted safe char as per the RFC
|
||||
|
||||
- Added some tests from the schooltool icalendar parser for better coverage
|
||||
|
||||
- Be more forgiving on the regex for folding lines
|
||||
|
||||
- Allow for multiple top-level components on .from_string
|
||||
|
||||
- Fix vWeekdays, wasn't accepting relative param (eg: -3SA vs -SA)
|
||||
|
||||
- vDDDTypes didn't accept negative period (eg: -P30M)
|
||||
|
||||
- 'N' is also acceptable as newline on content lines, per RFC
|
||||
|
||||
|
||||
0.10 (2005-04-28)
|
||||
-----------------
|
||||
|
||||
- moved code to codespeak.net subversion.
|
||||
|
||||
- reorganized package structure so that source code is under 'src' directory.
|
||||
Non-package files remain in distribution root.
|
||||
|
||||
- redid doc/.py files as doc/.txt, using more modern doctest. Before they
|
||||
were .py files with big docstrings.
|
||||
|
||||
- added test.py testrunner, and tests/test_icalendar.py that picks up all
|
||||
doctests in source code and doc directory, and runs them, when typing::
|
||||
|
||||
python2.3 test.py
|
||||
|
||||
- renamed iCalendar to lower case package name, lowercased, de-pluralized and
|
||||
shorted module names, which are mostly implementation detail.
|
||||
|
||||
- changed tests so they generate .ics files in a temp directory, not in the
|
||||
structure itself.
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
Copyright (c) 2012-2013, Plone Foundation
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Keywords: calendar calendaring ical icalendar event todo journal recurring
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
96
libs/icalendar-3.6.1/README.rst
Normal file
96
libs/icalendar-3.6.1/README.rst
Normal file
@ -0,0 +1,96 @@
|
||||
==========================================================
|
||||
Internet Calendaring and Scheduling (iCalendar) for Python
|
||||
==========================================================
|
||||
|
||||
The `icalendar`_ package is a parser/generator of iCalendar files for use
|
||||
with Python.
|
||||
|
||||
----
|
||||
|
||||
:Homepage: http://icalendar.readthedocs.org
|
||||
:Code: http://github.com/collective/icalendar
|
||||
:Mailing list: http://github.com/collective/icalendar/issues
|
||||
:Dependencies: `setuptools`_ and since version 3.0 we depend on `pytz`_.
|
||||
:Compatible with: Python 2.6, 2.7 and 3.3+
|
||||
:License: `BSD`_
|
||||
|
||||
----
|
||||
|
||||
|
||||
Roadmap
|
||||
=======
|
||||
|
||||
- 3.6: Python 3 support (current version)
|
||||
|
||||
- 4.0: API refactoring
|
||||
|
||||
|
||||
|
||||
Changes in version 3.0
|
||||
======================
|
||||
|
||||
API Change
|
||||
----------
|
||||
|
||||
Since version we unified to icalendar de/serialization API to use only to_ical
|
||||
(for writing an ical string from the internal representation) and from_ical
|
||||
(for parsing an ical string into the internal representation).
|
||||
|
||||
to_ical is now used instead of the methods ical, string, as_string and instead
|
||||
of string casting via __str__ and str.
|
||||
|
||||
from_ical is now used instead of from_string.
|
||||
|
||||
This change is a requirement for future Python 3 compatibility. Please update
|
||||
your code to reflect to the new API.
|
||||
|
||||
Timezone support
|
||||
----------------
|
||||
|
||||
Timezones are now fully supported in icalendar for serialization and
|
||||
deserialization. We use the pytz library for timezone components of datetime
|
||||
instances. The timezone identifiers must be valid pytz respectively Olson
|
||||
database timezone identifiers. This can be a problem for 'GMT' identifiers,
|
||||
which are not defined in the Olson database.
|
||||
|
||||
Instead of the own UTC tzinfo implementation we use pytz UTC tzinfo object now.
|
||||
|
||||
|
||||
About this fork which is not a fork anymore
|
||||
===========================================
|
||||
|
||||
Aim of this fork (not fork anymore, read further) was to bring this package up
|
||||
to date with latest icalendar `RFC`_ specification as part of
|
||||
`plone.app.event`_ project which goal is to bring recurrent evens to `Plone`_.
|
||||
|
||||
After some thoughts we (Plone developers involved with `plone.app.event`_) send
|
||||
a suggestion to icalendar-dev@codespeak.net to take over mainaining of
|
||||
`icalendar`_. Nobody object and since version 2.2 we are back to development.
|
||||
|
||||
.. _`icalendar`: http://pypi.python.org/pypi/icalendar
|
||||
.. _`plone.app.event`: http://github.com/plone/plone.app.event
|
||||
.. _`Plone`: http://plone.org
|
||||
.. _`pytz`: http://pypi.python.org/pypi/pytz
|
||||
.. _`setuptools`: http://pypi.python.org/pypi/setuptools
|
||||
.. _`RFC`: http://www.ietf.org/rfc/rfc5545.txt
|
||||
.. _`BSD`: https://github.com/collective/icalendar/issues/2
|
||||
|
||||
|
||||
Test Coverage Report
|
||||
====================
|
||||
|
||||
Output from coverage test::
|
||||
|
||||
Name Stmts Miss Cover
|
||||
----------------------------------------------------------------------------------
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/__init__ 5 0 100%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/cal 234 7 97%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/caselessdict 55 5 91%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/compat 1 0 100%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/parser 189 6 97%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/parser_tools 20 0 100%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/prop 533 62 88%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/tools 16 0 100%
|
||||
----------------------------------------------------------------------------------
|
||||
TOTAL 1053 80 92%
|
||||
|
26
libs/icalendar-3.6.1/TODO.rst
Normal file
26
libs/icalendar-3.6.1/TODO.rst
Normal file
@ -0,0 +1,26 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
- Update docs.
|
||||
|
||||
- Add a __add__ method to cal.Component, so that ``cal[key] = val`` works as
|
||||
expected. Currently, the value is added as is, but not converted to the
|
||||
correct subcomponent, as specified in prop.TypesFactory. See also the NOTE
|
||||
in: icalendar.tests.example.rst, Components, line 82.
|
||||
|
||||
- Eventually implement a ``decoded`` method for all icalendar.prop properties,
|
||||
so that cal.decoded doesn't call the from_ical methods but decode it into
|
||||
realy python natives. We want from_ical encode a ical string into a
|
||||
icalendar.prop instance, so decoding into a python native seems not to be
|
||||
appropriate there. (but the vDDD-types are encoded into python natives, so
|
||||
there is an inconsistence...)
|
||||
|
||||
OLD TODO's
|
||||
==========
|
||||
|
||||
- Check and Fix VTIMEZONE component functionality and creating VTIMEZONE
|
||||
components from tzinfo instances.
|
||||
|
||||
- Automatic encoding and decoding of parameter values. Most of the
|
||||
work is done already. Just need to get it finished. Look at line 153
|
||||
in 'src/icalendar/parser.py'
|
59
libs/icalendar-3.6.1/build/lib/icalendar/__init__.py
Normal file
59
libs/icalendar-3.6.1/build/lib/icalendar/__init__.py
Normal file
@ -0,0 +1,59 @@
|
||||
from icalendar.cal import (
|
||||
Calendar,
|
||||
Event,
|
||||
Todo,
|
||||
Journal,
|
||||
Timezone,
|
||||
TimezoneStandard,
|
||||
TimezoneDaylight,
|
||||
FreeBusy,
|
||||
Alarm,
|
||||
ComponentFactory,
|
||||
)
|
||||
# Property Data Value Types
|
||||
from icalendar.prop import (
|
||||
vBinary,
|
||||
vBoolean,
|
||||
vCalAddress,
|
||||
vDatetime,
|
||||
vDate,
|
||||
vDDDTypes,
|
||||
vDuration,
|
||||
vFloat,
|
||||
vInt,
|
||||
vPeriod,
|
||||
vWeekday,
|
||||
vFrequency,
|
||||
vRecur,
|
||||
vText,
|
||||
vTime,
|
||||
vUri,
|
||||
vGeo,
|
||||
vUTCOffset,
|
||||
TypesFactory,
|
||||
)
|
||||
# useful tzinfo subclasses
|
||||
from icalendar.prop import (
|
||||
FixedOffset,
|
||||
LocalTimezone,
|
||||
)
|
||||
# Parameters and helper methods for splitting and joining string with escaped
|
||||
# chars.
|
||||
from icalendar.parser import (
|
||||
Parameters,
|
||||
q_split,
|
||||
q_join,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
Calendar, Event, Todo, Journal,
|
||||
FreeBusy, Alarm, ComponentFactory,
|
||||
Timezone, TimezoneStandard, TimezoneDaylight,
|
||||
vBinary, vBoolean, vCalAddress, vDatetime, vDate,
|
||||
vDDDTypes, vDuration, vFloat, vInt, vPeriod,
|
||||
vWeekday, vFrequency, vRecur, vText, vTime, vUri,
|
||||
vGeo, vUTCOffset, TypesFactory,
|
||||
FixedOffset, LocalTimezone,
|
||||
Parameters, q_split, q_join,
|
||||
]
|
493
libs/icalendar-3.6.1/build/lib/icalendar/cal.py
Normal file
493
libs/icalendar-3.6.1/build/lib/icalendar/cal.py
Normal file
@ -0,0 +1,493 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Calendar is a dictionary like Python object that can render itself as VCAL
|
||||
files according to rfc2445.
|
||||
|
||||
These are the defined components.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from icalendar.caselessdict import CaselessDict
|
||||
from icalendar.parser import Contentline
|
||||
from icalendar.parser import Contentlines
|
||||
from icalendar.parser import Parameters
|
||||
from icalendar.parser import q_join
|
||||
from icalendar.parser import q_split
|
||||
from icalendar.parser_tools import DEFAULT_ENCODING
|
||||
from icalendar.parser_tools import data_encode
|
||||
from icalendar.prop import TypesFactory
|
||||
from icalendar.prop import vText, vDDDLists
|
||||
|
||||
import pytz
|
||||
|
||||
|
||||
######################################
|
||||
# The component factory
|
||||
|
||||
class ComponentFactory(CaselessDict):
|
||||
"""All components defined in rfc 2445 are registered in this factory class.
|
||||
To get a component you can use it like this.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Set keys to upper for initial dict.
|
||||
"""
|
||||
CaselessDict.__init__(self, *args, **kwargs)
|
||||
self['VEVENT'] = Event
|
||||
self['VTODO'] = Todo
|
||||
self['VJOURNAL'] = Journal
|
||||
self['VFREEBUSY'] = FreeBusy
|
||||
self['VTIMEZONE'] = Timezone
|
||||
self['STANDARD'] = TimezoneStandard
|
||||
self['DAYLIGHT'] = TimezoneDaylight
|
||||
self['VALARM'] = Alarm
|
||||
self['VCALENDAR'] = Calendar
|
||||
|
||||
|
||||
# These Properties have multiple property values inlined in one propertyline
|
||||
# seperated by comma. Use CaselessDict as simple caseless set.
|
||||
INLINE = CaselessDict(
|
||||
[(cat, 1) for cat in ('CATEGORIES', 'RESOURCES', 'FREEBUSY')]
|
||||
)
|
||||
|
||||
_marker = []
|
||||
|
||||
|
||||
class Component(CaselessDict):
|
||||
"""Component is the base object for calendar, Event and the other
|
||||
components defined in RFC 2445. normally you will not use this class
|
||||
directy, but rather one of the subclasses.
|
||||
"""
|
||||
|
||||
name = '' # must be defined in each component
|
||||
required = () # These properties are required
|
||||
singletons = () # These properties must only appear once
|
||||
multiple = () # may occur more than once
|
||||
exclusive = () # These properties are mutually exclusive
|
||||
inclusive = () # if any occurs the other(s) MUST occur
|
||||
# ('duration', 'repeat')
|
||||
ignore_exceptions = False # if True, and we cannot parse this
|
||||
# component, we will silently ignore
|
||||
# it, rather than let the exception
|
||||
# propagate upwards
|
||||
# not_compliant = [''] # List of non-compliant properties.
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Set keys to upper for initial dict.
|
||||
"""
|
||||
CaselessDict.__init__(self, *args, **kwargs)
|
||||
# set parameters here for properties that use non-default values
|
||||
self.subcomponents = [] # Components can be nested.
|
||||
self.is_broken = False # True if we ignored an exception while
|
||||
# parsing a property
|
||||
|
||||
#def is_compliant(self, name):
|
||||
# """Returns True is the given property name is compliant with the
|
||||
# icalendar implementation.
|
||||
#
|
||||
# If the parser is too strict it might prevent parsing erroneous but
|
||||
# otherwise compliant properties. So the parser is pretty lax, but it is
|
||||
# possible to test for non-complience by calling this method.
|
||||
# """
|
||||
# return name in not_compliant
|
||||
|
||||
#############################
|
||||
# handling of property values
|
||||
|
||||
def _encode(self, name, value, parameters=None, encode=1):
|
||||
"""Encode values to icalendar property values.
|
||||
|
||||
:param name: Name of the property.
|
||||
:type name: string
|
||||
|
||||
:param value: Value of the property. Either of a basic Python type of
|
||||
any of the icalendar's own property types.
|
||||
:type value: Python native type or icalendar property type.
|
||||
|
||||
:param parameters: Property parameter dictionary for the value. Only
|
||||
available, if encode is set to True.
|
||||
:type parameters: Dictionary
|
||||
|
||||
:param encode: True, if the value should be encoded to one of
|
||||
icalendar's own property types (Fallback is "vText")
|
||||
or False, if not.
|
||||
:type encode: Boolean
|
||||
|
||||
:returns: icalendar property value
|
||||
"""
|
||||
if not encode:
|
||||
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)
|
||||
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
|
||||
return obj
|
||||
|
||||
def add(self, name, value, parameters=None, encode=1):
|
||||
"""Add a property.
|
||||
|
||||
:param name: Name of the property.
|
||||
:type name: string
|
||||
|
||||
:param value: Value of the property. Either of a basic Python type of
|
||||
any of the icalendar's own property types.
|
||||
:type value: Python native type or icalendar property type.
|
||||
|
||||
:param parameters: Property parameter dictionary for the value. Only
|
||||
available, if encode is set to True.
|
||||
:type parameters: Dictionary
|
||||
|
||||
:param encode: True, if the value should be encoded to one of
|
||||
icalendar's own property types (Fallback is "vText")
|
||||
or False, if not.
|
||||
:type encode: Boolean
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
if isinstance(value, datetime) and\
|
||||
name.lower() in ('dtstamp', 'created', 'last-modified'):
|
||||
# RFC expects UTC for those... force value conversion.
|
||||
if getattr(value, 'tzinfo', False) and value.tzinfo is not None:
|
||||
value = value.astimezone(pytz.utc)
|
||||
else:
|
||||
# assume UTC for naive datetime instances
|
||||
value = pytz.utc.localize(value)
|
||||
|
||||
# encode value
|
||||
if encode and isinstance(value, list) \
|
||||
and name.lower() not in ['rdate', 'exdate']:
|
||||
# Individually convert each value to an ical type except rdate and
|
||||
# exdate, where lists of dates might be passed to vDDDLists.
|
||||
value = [self._encode(name, v, parameters, encode) for v in value]
|
||||
else:
|
||||
value = self._encode(name, value, parameters, encode)
|
||||
|
||||
# set value
|
||||
if name in self:
|
||||
# If property already exists, append it.
|
||||
#if name == 'attendee': import pdb; pdb.set_trace()
|
||||
oldval = self[name]
|
||||
if isinstance(oldval, list):
|
||||
if isinstance(value, list):
|
||||
value = oldval + value
|
||||
else:
|
||||
oldval.append(value)
|
||||
value = oldval
|
||||
else:
|
||||
value = [oldval, value]
|
||||
self[name] = value
|
||||
|
||||
def _decode(self, name, value):
|
||||
"""Internal for decoding property values.
|
||||
"""
|
||||
|
||||
# TODO: Currently the decoded method calls the icalendar.prop instances
|
||||
# from_ical. We probably want to decode properties into Python native
|
||||
# types here. But when parsing from an ical string with from_ical, we
|
||||
# want to encode the string into a real icalendar.prop property.
|
||||
if isinstance(value, vDDDLists):
|
||||
# TODO: Workaround unfinished decoding
|
||||
return value
|
||||
decoded = types_factory.from_ical(name, value)
|
||||
# TODO: remove when proper decoded is implemented in every prop.* class
|
||||
# Workaround to decode vText properly
|
||||
if isinstance(decoded, vText):
|
||||
decoded = decoded.encode(DEFAULT_ENCODING)
|
||||
return decoded
|
||||
|
||||
def decoded(self, name, default=_marker):
|
||||
"""Returns decoded value of property.
|
||||
"""
|
||||
# XXX: fail. what's this function supposed to do in the end?
|
||||
# -rnix
|
||||
|
||||
if name in self:
|
||||
value = self[name]
|
||||
if isinstance(value, list):
|
||||
return [self._decode(name, v) for v in value]
|
||||
return self._decode(name, value)
|
||||
else:
|
||||
if default is _marker:
|
||||
raise KeyError(name)
|
||||
else:
|
||||
return default
|
||||
|
||||
########################################################################
|
||||
# Inline values. A few properties have multiple values inlined in in one
|
||||
# property line. These methods are used for splitting and joining these.
|
||||
|
||||
def get_inline(self, name, decode=1):
|
||||
"""Returns a list of values (split on comma).
|
||||
"""
|
||||
vals = [v.strip('" ') for v in q_split(self[name])]
|
||||
if decode:
|
||||
return [self._decode(name, val) for val in vals]
|
||||
return vals
|
||||
|
||||
def set_inline(self, name, values, encode=1):
|
||||
"""Converts a list of values into comma seperated string and sets value
|
||||
to that.
|
||||
"""
|
||||
if encode:
|
||||
values = [self._encode(name, value, encode=1) for value in values]
|
||||
self[name] = types_factory['inline'](q_join(values))
|
||||
|
||||
#########################
|
||||
# Handling of components
|
||||
|
||||
def add_component(self, component):
|
||||
"""Add a subcomponent to this component.
|
||||
"""
|
||||
self.subcomponents.append(component)
|
||||
|
||||
def _walk(self, name):
|
||||
"""Walk to given component.
|
||||
"""
|
||||
result = []
|
||||
if name is None or self.name == name:
|
||||
result.append(self)
|
||||
for subcomponent in self.subcomponents:
|
||||
result += subcomponent._walk(name)
|
||||
return result
|
||||
|
||||
def walk(self, name=None):
|
||||
"""Recursively traverses component and subcomponents. Returns sequence
|
||||
of same. If name is passed, only components with name will be returned.
|
||||
"""
|
||||
if not name is None:
|
||||
name = name.upper()
|
||||
return self._walk(name)
|
||||
|
||||
#####################
|
||||
# Generation
|
||||
|
||||
def property_items(self, recursive=True):
|
||||
"""Returns properties in this component and subcomponents as:
|
||||
[(name, value), ...]
|
||||
"""
|
||||
vText = types_factory['text']
|
||||
properties = [('BEGIN', vText(self.name).to_ical())]
|
||||
property_names = self.sorted_keys()
|
||||
for name in property_names:
|
||||
values = self[name]
|
||||
if isinstance(values, list):
|
||||
# normally one property is one line
|
||||
for value in values:
|
||||
properties.append((name, value))
|
||||
else:
|
||||
properties.append((name, values))
|
||||
if recursive:
|
||||
# recursion is fun!
|
||||
for subcomponent in self.subcomponents:
|
||||
properties += subcomponent.property_items()
|
||||
properties.append(('END', vText(self.name).to_ical()))
|
||||
return properties
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, st, multiple=False):
|
||||
"""Populates the component recursively from a string.
|
||||
"""
|
||||
stack = [] # a stack of components
|
||||
comps = []
|
||||
for line in Contentlines.from_ical(st): # raw parsing
|
||||
if not line:
|
||||
continue
|
||||
name, params, vals = line.parts()
|
||||
uname = name.upper()
|
||||
# check for start of component
|
||||
if uname == 'BEGIN':
|
||||
# try and create one of the components defined in the spec,
|
||||
# otherwise get a general Components for robustness.
|
||||
c_name = vals.upper()
|
||||
c_class = component_factory.get(c_name, cls)
|
||||
component = c_class()
|
||||
if not getattr(component, 'name', ''): # undefined components
|
||||
component.name = c_name
|
||||
stack.append(component)
|
||||
# check for end of event
|
||||
elif uname == 'END':
|
||||
# we are done adding properties to this component
|
||||
# so pop it from the stack and add it to the new top.
|
||||
component = stack.pop()
|
||||
if not stack: # we are at the end
|
||||
comps.append(component)
|
||||
else:
|
||||
if not component.is_broken:
|
||||
stack[-1].add_component(component)
|
||||
# we are adding properties to the current top of the stack
|
||||
else:
|
||||
factory = types_factory.for_property(name)
|
||||
component = stack[-1]
|
||||
datetime_names = ('DTSTART', 'DTEND', 'RECURRENCE-ID', 'DUE',
|
||||
'FREEBUSY', 'RDATE', 'EXDATE')
|
||||
try:
|
||||
if name in datetime_names and 'TZID' in params:
|
||||
vals = factory(factory.from_ical(vals, params['TZID']))
|
||||
else:
|
||||
vals = factory(factory.from_ical(vals))
|
||||
except ValueError:
|
||||
if not component.ignore_exceptions:
|
||||
raise
|
||||
component.is_broken = True
|
||||
else:
|
||||
vals.params = params
|
||||
component.add(name, vals, encode=0)
|
||||
|
||||
if multiple:
|
||||
return comps
|
||||
if len(comps) > 1:
|
||||
raise ValueError('Found multiple components where '
|
||||
'only one is allowed: {st!r}'.format(**locals()))
|
||||
if len(comps) < 1:
|
||||
raise ValueError('Found no components where '
|
||||
'exactly one is required: '
|
||||
'{st!r}'.format(**locals()))
|
||||
return comps[0]
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.name, data_encode(self))
|
||||
|
||||
def content_line(self, name, value):
|
||||
"""Returns property as content line.
|
||||
"""
|
||||
params = getattr(value, 'params', Parameters())
|
||||
return Contentline.from_parts(name, params, value)
|
||||
|
||||
def content_lines(self):
|
||||
"""Converts the Component and subcomponents into content lines.
|
||||
"""
|
||||
contentlines = Contentlines()
|
||||
for name, value in self.property_items():
|
||||
cl = self.content_line(name, value)
|
||||
contentlines.append(cl)
|
||||
contentlines.append('') # remember the empty string in the end
|
||||
return contentlines
|
||||
|
||||
def to_ical(self):
|
||||
content_lines = self.content_lines()
|
||||
return content_lines.to_ical()
|
||||
|
||||
|
||||
#######################################
|
||||
# components defined in RFC 2445
|
||||
|
||||
class Event(Component):
|
||||
|
||||
name = 'VEVENT'
|
||||
|
||||
canonical_order = (
|
||||
'SUMMARY', 'DTSTART', 'DTEND', 'DURATION', 'DTSTAMP',
|
||||
'UID', 'RECURRENCE-ID', 'SEQUENCE',
|
||||
'RRULE' 'EXRULE', 'RDATE', 'EXDATE',
|
||||
)
|
||||
|
||||
required = ('UID',)
|
||||
singletons = (
|
||||
'CLASS', 'CREATED', 'DESCRIPTION', 'DTSTART', 'GEO', 'LAST-MODIFIED',
|
||||
'LOCATION', 'ORGANIZER', 'PRIORITY', 'DTSTAMP', 'SEQUENCE', 'STATUS',
|
||||
'SUMMARY', 'TRANSP', 'URL', 'RECURRENCE-ID', 'DTEND', 'DURATION',
|
||||
'DTSTART',
|
||||
)
|
||||
exclusive = ('DTEND', 'DURATION', )
|
||||
multiple = (
|
||||
'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
|
||||
'EXRULE', 'RSTATUS', 'RELATED', 'RESOURCES', 'RDATE', 'RRULE'
|
||||
)
|
||||
ignore_exceptions = True
|
||||
|
||||
|
||||
class Todo(Component):
|
||||
|
||||
name = 'VTODO'
|
||||
|
||||
required = ('UID',)
|
||||
singletons = (
|
||||
'CLASS', 'COMPLETED', 'CREATED', 'DESCRIPTION', 'DTSTAMP', 'DTSTART',
|
||||
'GEO', 'LAST-MODIFIED', 'LOCATION', 'ORGANIZER', 'PERCENT', 'PRIORITY',
|
||||
'RECURRENCE-ID', 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL', 'DUE',
|
||||
'DURATION',
|
||||
)
|
||||
exclusive = ('DUE', 'DURATION',)
|
||||
multiple = (
|
||||
'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
|
||||
'EXRULE', 'RSTATUS', 'RELATED', 'RESOURCES', 'RDATE', 'RRULE'
|
||||
)
|
||||
|
||||
|
||||
class Journal(Component):
|
||||
|
||||
name = 'VJOURNAL'
|
||||
|
||||
required = ('UID',)
|
||||
singletons = (
|
||||
'CLASS', 'CREATED', 'DESCRIPTION', 'DTSTART', 'DTSTAMP',
|
||||
'LAST-MODIFIED', 'ORGANIZER', 'RECURRENCE-ID', 'SEQUENCE', 'STATUS',
|
||||
'SUMMARY', 'UID', 'URL',
|
||||
)
|
||||
multiple = (
|
||||
'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
|
||||
'EXRULE', 'RELATED', 'RDATE', 'RRULE', 'RSTATUS',
|
||||
)
|
||||
|
||||
|
||||
class FreeBusy(Component):
|
||||
|
||||
name = 'VFREEBUSY'
|
||||
|
||||
required = ('UID',)
|
||||
singletons = (
|
||||
'CONTACT', 'DTSTART', 'DTEND', 'DURATION', 'DTSTAMP', 'ORGANIZER',
|
||||
'UID', 'URL',
|
||||
)
|
||||
multiple = ('ATTENDEE', 'COMMENT', 'FREEBUSY', 'RSTATUS',)
|
||||
|
||||
|
||||
class Timezone(Component):
|
||||
name = 'VTIMEZONE'
|
||||
canonical_order = ('TZID', 'STANDARD', 'DAYLIGHT',)
|
||||
required = ('TZID', 'STANDARD', 'DAYLIGHT',)
|
||||
singletons = ('TZID', 'LAST-MODIFIED', 'TZURL',)
|
||||
|
||||
|
||||
class TimezoneStandard(Component):
|
||||
name = 'STANDARD'
|
||||
required = ('DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM')
|
||||
singletons = ('DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM', 'RRULE')
|
||||
multiple = ('COMMENT', 'RDATE', 'TZNAME')
|
||||
|
||||
|
||||
class TimezoneDaylight(Component):
|
||||
name = 'DAYLIGHT'
|
||||
required = ('DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM')
|
||||
singletons = ('DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM', 'RRULE')
|
||||
multiple = ('COMMENT', 'RDATE', 'TZNAME')
|
||||
|
||||
|
||||
class Alarm(Component):
|
||||
|
||||
name = 'VALARM'
|
||||
# not quite sure about these ...
|
||||
required = ('ACTION', 'TRIGGER',)
|
||||
singletons = ('ATTACH', 'ACTION', 'TRIGGER', 'DURATION', 'REPEAT',)
|
||||
inclusive = (('DURATION', 'REPEAT',),)
|
||||
|
||||
|
||||
class Calendar(Component):
|
||||
"""This is the base object for an iCalendar file.
|
||||
"""
|
||||
name = 'VCALENDAR'
|
||||
canonical_order = ('VERSION', 'PRODID', 'CALSCALE', 'METHOD',)
|
||||
required = ('prodid', 'version', )
|
||||
singletons = ('prodid', 'version', )
|
||||
multiple = ('calscale', 'method', )
|
||||
|
||||
# These are read only singleton, so one instance is enough for the module
|
||||
types_factory = TypesFactory()
|
||||
component_factory = ComponentFactory()
|
98
libs/icalendar-3.6.1/build/lib/icalendar/caselessdict.py
Normal file
98
libs/icalendar-3.6.1/build/lib/icalendar/caselessdict.py
Normal file
@ -0,0 +1,98 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from icalendar.parser_tools import to_unicode
|
||||
from icalendar.parser_tools import data_encode
|
||||
|
||||
|
||||
def canonsort_keys(keys, canonical_order=None):
|
||||
"""Sorts leading keys according to canonical_order. Keys not specified in
|
||||
canonical_order will appear alphabetically at the end.
|
||||
"""
|
||||
canonical_map = dict((k, i) for i, k in enumerate(canonical_order or []))
|
||||
head = [k for k in keys if k in canonical_map]
|
||||
tail = [k for k in keys if k not in canonical_map]
|
||||
return sorted(head, key=lambda k: canonical_map[k]) + sorted(tail)
|
||||
|
||||
|
||||
def canonsort_items(dict1, canonical_order=None):
|
||||
"""Returns a list of items from dict1, sorted by canonical_order.
|
||||
"""
|
||||
return [(k, dict1[k]) for \
|
||||
k in canonsort_keys(dict1.keys(), canonical_order)]
|
||||
|
||||
|
||||
class CaselessDict(dict):
|
||||
"""A dictionary that isn't case sensitive, and only uses strings as keys.
|
||||
Values retain their case.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Set keys to upper for initial dict.
|
||||
"""
|
||||
dict.__init__(self, *args, **kwargs)
|
||||
for key, value in self.items():
|
||||
key_upper = to_unicode(key).upper()
|
||||
if key != key_upper:
|
||||
dict.__delitem__(self, key)
|
||||
self[key_upper] = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
key = to_unicode(key)
|
||||
return dict.__getitem__(self, key.upper())
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
key = to_unicode(key)
|
||||
dict.__setitem__(self, key.upper(), value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
key = to_unicode(key)
|
||||
dict.__delitem__(self, key.upper())
|
||||
|
||||
def __contains__(self, key):
|
||||
key = to_unicode(key)
|
||||
return dict.__contains__(self, key.upper())
|
||||
|
||||
def get(self, key, default=None):
|
||||
key = to_unicode(key)
|
||||
return dict.get(self, key.upper(), default)
|
||||
|
||||
def setdefault(self, key, value=None):
|
||||
key = to_unicode(key)
|
||||
return dict.setdefault(self, key.upper(), value)
|
||||
|
||||
def pop(self, key, default=None):
|
||||
key = to_unicode(key)
|
||||
return dict.pop(self, key.upper(), default)
|
||||
|
||||
def popitem(self):
|
||||
return dict.popitem(self)
|
||||
|
||||
def has_key(self, key):
|
||||
key = to_unicode(key)
|
||||
return dict.__contains__(self, key.upper())
|
||||
|
||||
def update(self, indict):
|
||||
# Multiple keys where key1.upper() == key2.upper() will be lost.
|
||||
for key, value in indict.items(): # TODO optimize in python 2
|
||||
self[key] = value
|
||||
|
||||
def copy(self):
|
||||
return CaselessDict(dict.copy(self))
|
||||
|
||||
def __repr__(self):
|
||||
return 'CaselessDict(%s)' % data_encode(self)
|
||||
|
||||
# A list of keys that must appear first in sorted_keys and sorted_items;
|
||||
# must be uppercase.
|
||||
canonical_order = None
|
||||
|
||||
def sorted_keys(self):
|
||||
"""Sorts keys according to the canonical_order for the derived class.
|
||||
Keys not specified in canonical_order will appear at the end.
|
||||
"""
|
||||
return canonsort_keys(self.keys(), self.canonical_order)
|
||||
|
||||
def sorted_items(self):
|
||||
"""Sorts items according to the canonical_order for the derived class.
|
||||
Items not specified in canonical_order will appear at the end.
|
||||
"""
|
||||
return canonsort_items(self, self.canonical_order)
|
11
libs/icalendar-3.6.1/build/lib/icalendar/compat.py
Normal file
11
libs/icalendar-3.6.1/build/lib/icalendar/compat.py
Normal file
@ -0,0 +1,11 @@
|
||||
import sys
|
||||
|
||||
|
||||
if sys.version_info[0] == 2: # pragma: no cover
|
||||
unicode_type = unicode
|
||||
bytes_type = str
|
||||
iteritems = lambda d, *args, **kwargs: iter(d.iteritems(*args, **kwargs))
|
||||
else: # pragma: no cover
|
||||
unicode_type = str
|
||||
bytes_type = bytes
|
||||
iteritems = lambda d, *args, **kwargs: iter(d.items(*args, **kwargs))
|
371
libs/icalendar-3.6.1/build/lib/icalendar/parser.py
Normal file
371
libs/icalendar-3.6.1/build/lib/icalendar/parser.py
Normal file
@ -0,0 +1,371 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""This module parses and generates contentlines as defined in RFC 2445
|
||||
(iCalendar), but will probably work for other MIME types with similar syntax.
|
||||
Eg. RFC 2426 (vCard)
|
||||
|
||||
It is stupid in the sense that it treats the content purely as strings. No type
|
||||
conversion is attempted.
|
||||
"""
|
||||
from icalendar import compat
|
||||
from icalendar.caselessdict import CaselessDict
|
||||
from icalendar.parser_tools import DEFAULT_ENCODING
|
||||
from icalendar.parser_tools import SEQUENCE_TYPES
|
||||
from icalendar.parser_tools import data_encode
|
||||
from icalendar.parser_tools import to_unicode
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def escape_char(text):
|
||||
"""Format value according to iCalendar TEXT escaping rules.
|
||||
"""
|
||||
assert isinstance(text, (compat.unicode_type, compat.bytes_type))
|
||||
# NOTE: ORDER MATTERS!
|
||||
return text.replace(r'\N', '\n')\
|
||||
.replace('\\', '\\\\')\
|
||||
.replace(';', r'\;')\
|
||||
.replace(',', r'\,')\
|
||||
.replace('\r\n', r'\n')\
|
||||
.replace('\n', r'\n')
|
||||
|
||||
|
||||
def unescape_char(text):
|
||||
assert isinstance(text, (compat.unicode_type, compat.bytes_type))
|
||||
# NOTE: ORDER MATTERS!
|
||||
if isinstance(text, compat.unicode_type):
|
||||
return text.replace(u'\\N', u'\\n')\
|
||||
.replace(u'\r\n', u'\n')\
|
||||
.replace(u'\\n', u'\n')\
|
||||
.replace(u'\\,', u',')\
|
||||
.replace(u'\\;', u';')\
|
||||
.replace(u'\\\\', u'\\')
|
||||
elif isinstance(text, compat.bytes_type):
|
||||
return text.replace(b'\N', b'\n')\
|
||||
.replace(b'\r\n', b'\n')\
|
||||
.replace(b'\n', b'\n')\
|
||||
.replace(b'\,', b',')\
|
||||
.replace(b'\;', b';')\
|
||||
.replace(b'\\\\', b'\\')
|
||||
|
||||
|
||||
def tzid_from_dt(dt):
|
||||
tzid = None
|
||||
if hasattr(dt.tzinfo, 'zone'):
|
||||
tzid = dt.tzinfo.zone # pytz implementation
|
||||
elif hasattr(dt.tzinfo, 'tzname'):
|
||||
try:
|
||||
tzid = dt.tzinfo.tzname(dt) # dateutil implementation
|
||||
except AttributeError:
|
||||
# No tzid available
|
||||
pass
|
||||
return tzid
|
||||
|
||||
|
||||
def foldline(line, limit=75, fold_sep=u'\r\n '):
|
||||
"""Make a string folded as defined in RFC5545
|
||||
Lines of text SHOULD NOT be longer than 75 octets, excluding the line
|
||||
break. Long content lines SHOULD be split into a multiple line
|
||||
representations using a line "folding" technique. That is, a long
|
||||
line can be split between any two characters by inserting a CRLF
|
||||
immediately followed by a single linear white-space character (i.e.,
|
||||
SPACE or HTAB).
|
||||
"""
|
||||
assert isinstance(line, compat.unicode_type)
|
||||
assert u'\n' not in line
|
||||
|
||||
ret_line = u''
|
||||
byte_count = 0
|
||||
for char in line:
|
||||
char_byte_len = len(char.encode(DEFAULT_ENCODING))
|
||||
byte_count += char_byte_len
|
||||
if byte_count >= limit:
|
||||
ret_line += fold_sep
|
||||
byte_count = char_byte_len
|
||||
ret_line += char
|
||||
|
||||
return ret_line
|
||||
|
||||
|
||||
#################################################################
|
||||
# Property parameter stuff
|
||||
|
||||
def param_value(value):
|
||||
"""Returns a parameter value.
|
||||
"""
|
||||
if isinstance(value, SEQUENCE_TYPES):
|
||||
return q_join(value)
|
||||
return dquote(value)
|
||||
|
||||
|
||||
# Could be improved
|
||||
NAME = re.compile('[\w-]+')
|
||||
UNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F",:;]')
|
||||
QUNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F"]')
|
||||
FOLD = re.compile(b'(\r?\n)+[ \t]')
|
||||
uFOLD = re.compile(u'(\r?\n)+[ \t]')
|
||||
NEWLINE = re.compile(r'\r?\n')
|
||||
|
||||
|
||||
def validate_token(name):
|
||||
match = NAME.findall(name)
|
||||
if len(match) == 1 and name == match[0]:
|
||||
return
|
||||
raise ValueError(name)
|
||||
|
||||
|
||||
def validate_param_value(value, quoted=True):
|
||||
validator = QUNSAFE_CHAR if quoted else UNSAFE_CHAR
|
||||
if validator.findall(value):
|
||||
raise ValueError(value)
|
||||
|
||||
|
||||
# chars presence of which in parameter value will be cause the value
|
||||
# to be enclosed in double-quotes
|
||||
QUOTABLE = re.compile("[,;: ’']")
|
||||
|
||||
|
||||
def dquote(val):
|
||||
"""Enclose parameter values containing [,;:] in double quotes.
|
||||
"""
|
||||
# a double-quote character is forbidden to appear in a parameter value
|
||||
# so replace it with a single-quote character
|
||||
val = val.replace('"', "'")
|
||||
if QUOTABLE.search(val):
|
||||
return '"%s"' % val
|
||||
return val
|
||||
|
||||
|
||||
# parsing helper
|
||||
def q_split(st, sep=','):
|
||||
"""Splits a string on char, taking double (q)uotes into considderation.
|
||||
"""
|
||||
result = []
|
||||
cursor = 0
|
||||
length = len(st)
|
||||
inquote = 0
|
||||
for i in range(length):
|
||||
ch = st[i]
|
||||
if ch == '"':
|
||||
inquote = not inquote
|
||||
if not inquote and ch == sep:
|
||||
result.append(st[cursor:i])
|
||||
cursor = i + 1
|
||||
if i + 1 == length:
|
||||
result.append(st[cursor:])
|
||||
return result
|
||||
|
||||
|
||||
def q_join(lst, sep=','):
|
||||
"""Joins a list on sep, quoting strings with QUOTABLE chars.
|
||||
"""
|
||||
return sep.join(dquote(itm) for itm in lst)
|
||||
|
||||
|
||||
class Parameters(CaselessDict):
|
||||
"""Parser and generator of Property parameter strings. It knows nothing of
|
||||
datatypes. Its main concern is textual structure.
|
||||
"""
|
||||
|
||||
def params(self):
|
||||
"""In rfc2445 keys are called parameters, so this is to be consitent
|
||||
with the naming conventions.
|
||||
"""
|
||||
return self.keys()
|
||||
|
||||
# TODO?
|
||||
# Later, when I get more time... need to finish this off now. The last major
|
||||
# thing missing.
|
||||
# def _encode(self, name, value, cond=1):
|
||||
# # internal, for conditional convertion of values.
|
||||
# if cond:
|
||||
# klass = types_factory.for_property(name)
|
||||
# return klass(value)
|
||||
# return value
|
||||
#
|
||||
# def add(self, name, value, encode=0):
|
||||
# "Add a parameter value and optionally encode it."
|
||||
# if encode:
|
||||
# value = self._encode(name, value, encode)
|
||||
# self[name] = value
|
||||
#
|
||||
# def decoded(self, name):
|
||||
# "returns a decoded value, or list of same"
|
||||
|
||||
def __repr__(self):
|
||||
return 'Parameters(%s)' % data_encode(self)
|
||||
|
||||
def to_ical(self):
|
||||
result = []
|
||||
items = self.items()
|
||||
for key, value in sorted(items):
|
||||
value = param_value(value)
|
||||
if isinstance(value, compat.unicode_type):
|
||||
value = value.encode(DEFAULT_ENCODING)
|
||||
# CaselessDict keys are always unicode
|
||||
key = key.upper().encode(DEFAULT_ENCODING)
|
||||
result.append(key + b'=' + value)
|
||||
return b';'.join(result)
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, st, strict=False):
|
||||
"""Parses the parameter format from ical text format."""
|
||||
|
||||
# parse into strings
|
||||
result = cls()
|
||||
for param in q_split(st, ';'):
|
||||
try:
|
||||
key, val = q_split(param, '=')
|
||||
validate_token(key)
|
||||
# Property parameter values that are not in quoted
|
||||
# strings are case insensitive.
|
||||
vals = []
|
||||
for v in q_split(val, ','):
|
||||
if v.startswith('"') and v.endswith('"'):
|
||||
v = v.strip('"')
|
||||
validate_param_value(v, quoted=True)
|
||||
vals.append(v)
|
||||
else:
|
||||
validate_param_value(v, quoted=False)
|
||||
if strict:
|
||||
vals.append(v.upper())
|
||||
else:
|
||||
vals.append(v)
|
||||
if not vals:
|
||||
result[key] = val
|
||||
else:
|
||||
if len(vals) == 1:
|
||||
result[key] = vals[0]
|
||||
else:
|
||||
result[key] = vals
|
||||
except ValueError as exc:
|
||||
raise ValueError('%r is not a valid parameter string: %s'
|
||||
% (param, exc))
|
||||
return result
|
||||
|
||||
|
||||
def escape_string(val):
|
||||
# '%{:02X}'.format(i)
|
||||
return val.replace(r'\,', '%2C').replace(r'\:', '%3A')\
|
||||
.replace(r'\;', '%3B').replace(r'\\', '%5C')
|
||||
|
||||
|
||||
def unsescape_string(val):
|
||||
return val.replace('%2C', ',').replace('%3A', ':')\
|
||||
.replace('%3B', ';').replace('%5C', '\\')
|
||||
|
||||
|
||||
#########################################
|
||||
# parsing and generation of content lines
|
||||
|
||||
class Contentline(compat.unicode_type):
|
||||
"""A content line is basically a string that can be folded and parsed into
|
||||
parts.
|
||||
"""
|
||||
def __new__(cls, value, strict=False, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
assert u'\n' not in value, ('Content line can not contain unescaped '
|
||||
'new line characters.')
|
||||
self = super(Contentline, cls).__new__(cls, value)
|
||||
self.strict = strict
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_parts(cls, name, params, values):
|
||||
"""Turn a parts into a content line.
|
||||
"""
|
||||
assert isinstance(params, Parameters)
|
||||
if hasattr(values, 'to_ical'):
|
||||
values = values.to_ical()
|
||||
else:
|
||||
values = vText(values).to_ical()
|
||||
# elif isinstance(values, basestring):
|
||||
# values = escape_char(values)
|
||||
|
||||
# TODO: after unicode only, remove this
|
||||
# Convert back to unicode, after to_ical encoded it.
|
||||
name = to_unicode(name)
|
||||
values = to_unicode(values)
|
||||
if params:
|
||||
params = to_unicode(params.to_ical())
|
||||
return cls(u'%s;%s:%s' % (name, params, values))
|
||||
return cls(u'%s:%s' % (name, values))
|
||||
|
||||
def parts(self):
|
||||
"""Split the content line up into (name, parameters, values) parts.
|
||||
"""
|
||||
try:
|
||||
st = escape_string(self)
|
||||
name_split = None
|
||||
value_split = None
|
||||
in_quotes = False
|
||||
for i, ch in enumerate(st):
|
||||
if not in_quotes:
|
||||
if ch in ':;' and not name_split:
|
||||
name_split = i
|
||||
if ch == ':' and not value_split:
|
||||
value_split = i
|
||||
if ch == '"':
|
||||
in_quotes = not in_quotes
|
||||
name = unsescape_string(st[:name_split])
|
||||
if not name:
|
||||
raise ValueError('Key name is required')
|
||||
validate_token(name)
|
||||
if not name_split or name_split + 1 == value_split:
|
||||
raise ValueError('Invalid content line')
|
||||
params = Parameters.from_ical(st[name_split + 1: value_split],
|
||||
strict=self.strict)
|
||||
params = Parameters(
|
||||
(unsescape_string(key), unsescape_string(value))
|
||||
for key, value in compat.iteritems(params)
|
||||
)
|
||||
values = unsescape_string(st[value_split + 1:])
|
||||
return (name, params, values)
|
||||
except ValueError as exc:
|
||||
raise ValueError(
|
||||
u"Content line could not be parsed into parts: %r: %s"
|
||||
% (self, exc)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical, strict=False):
|
||||
"""Unfold the content lines in an iCalendar into long content lines.
|
||||
"""
|
||||
ical = to_unicode(ical)
|
||||
# a fold is carriage return followed by either a space or a tab
|
||||
return cls(uFOLD.sub('', ical), strict=strict)
|
||||
|
||||
def to_ical(self):
|
||||
"""Long content lines are folded so they are less than 75 characters
|
||||
wide.
|
||||
"""
|
||||
return foldline(self).encode(DEFAULT_ENCODING)
|
||||
|
||||
|
||||
class Contentlines(list):
|
||||
"""I assume that iCalendar files generally are a few kilobytes in size.
|
||||
Then this should be efficient. for Huge files, an iterator should probably
|
||||
be used instead.
|
||||
"""
|
||||
def to_ical(self):
|
||||
"""Simply join self.
|
||||
"""
|
||||
return b'\r\n'.join(line.to_ical() for line in self if line) + b'\r\n'
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, st):
|
||||
"""Parses a string into content lines.
|
||||
"""
|
||||
st = to_unicode(st)
|
||||
try:
|
||||
# a fold is carriage return followed by either a space or a tab
|
||||
unfolded = uFOLD.sub('', st)
|
||||
lines = cls(Contentline(line) for
|
||||
line in unfolded.splitlines() if line)
|
||||
lines.append('') # '\r\n' at the end of every content line
|
||||
return lines
|
||||
except:
|
||||
raise ValueError('Expected StringType with content lines')
|
||||
|
||||
|
||||
# XXX: what kind of hack is this? import depends to be at end
|
||||
from icalendar.prop import vText
|
33
libs/icalendar-3.6.1/build/lib/icalendar/parser_tools.py
Normal file
33
libs/icalendar-3.6.1/build/lib/icalendar/parser_tools.py
Normal file
@ -0,0 +1,33 @@
|
||||
from icalendar import compat
|
||||
|
||||
|
||||
SEQUENCE_TYPES = (list, tuple)
|
||||
DEFAULT_ENCODING = 'utf-8'
|
||||
|
||||
|
||||
def to_unicode(value, encoding='utf-8'):
|
||||
"""Converts a value to unicode, even if it is already a unicode string.
|
||||
"""
|
||||
if isinstance(value, compat.unicode_type):
|
||||
return value
|
||||
elif isinstance(value, compat.bytes_type):
|
||||
try:
|
||||
value = value.decode(encoding)
|
||||
except UnicodeDecodeError:
|
||||
value = value.decode('utf-8', 'replace')
|
||||
return value
|
||||
|
||||
|
||||
def data_encode(data, encoding=DEFAULT_ENCODING):
|
||||
"""Encode all datastructures to the given encoding.
|
||||
Currently unicode strings, dicts and lists are supported.
|
||||
"""
|
||||
# http://stackoverflow.com/questions/1254454/fastest-way-to-convert-a-dicts-keys-values-from-unicode-to-str
|
||||
if isinstance(data, compat.unicode_type):
|
||||
return data.encode(encoding)
|
||||
elif isinstance(data, dict):
|
||||
return dict(map(data_encode, compat.iteritems(data)))
|
||||
elif isinstance(data, list) or isinstance(data, tuple):
|
||||
return list(map(data_encode, data))
|
||||
else:
|
||||
return data
|
993
libs/icalendar-3.6.1/build/lib/icalendar/prop.py
Normal file
993
libs/icalendar-3.6.1/build/lib/icalendar/prop.py
Normal file
@ -0,0 +1,993 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""This module contains the parser/generators (or coders/encoders if you
|
||||
prefer) for the classes/datatypes that are used in iCalendar:
|
||||
|
||||
###########################################################################
|
||||
# This module defines these property value data types and property parameters
|
||||
|
||||
4.2 Defined property parameters are:
|
||||
|
||||
ALTREP, CN, CUTYPE, DELEGATED-FROM, DELEGATED-TO, DIR, ENCODING, FMTTYPE,
|
||||
FBTYPE, LANGUAGE, MEMBER, PARTSTAT, RANGE, RELATED, RELTYPE, ROLE, RSVP,
|
||||
SENT-BY, TZID, VALUE
|
||||
|
||||
4.3 Defined value data types are:
|
||||
|
||||
BINARY, BOOLEAN, CAL-ADDRESS, DATE, DATE-TIME, DURATION, FLOAT, INTEGER,
|
||||
PERIOD, RECUR, TEXT, TIME, URI, UTC-OFFSET
|
||||
|
||||
###########################################################################
|
||||
|
||||
iCalendar properties has values. The values are strongly typed. This module
|
||||
defines these types, calling val.to_ical() on them, Will render them as defined
|
||||
in rfc2445.
|
||||
|
||||
If you pass any of these classes a Python primitive, you will have an object
|
||||
that can render itself as iCalendar formatted date.
|
||||
|
||||
Property Value Data Types starts with a 'v'. they all have an to_ical() and
|
||||
from_ical() method. The to_ical() method generates a text string in the
|
||||
iCalendar format. The from_ical() method can parse this format and return a
|
||||
primitive Python datatype. So it should allways be true that:
|
||||
|
||||
x == vDataType.from_ical(VDataType(x).to_ical())
|
||||
|
||||
These types are mainly used for parsing and file generation. But you can set
|
||||
them directly.
|
||||
"""
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
from datetime import time
|
||||
from datetime import timedelta
|
||||
from datetime import tzinfo
|
||||
from dateutil.tz import tzutc
|
||||
from icalendar import compat
|
||||
from icalendar.caselessdict import CaselessDict
|
||||
from icalendar.parser import Parameters
|
||||
from icalendar.parser import escape_char
|
||||
from icalendar.parser import tzid_from_dt
|
||||
from icalendar.parser import unescape_char
|
||||
from icalendar.parser_tools import DEFAULT_ENCODING
|
||||
from icalendar.parser_tools import SEQUENCE_TYPES
|
||||
from icalendar.parser_tools import to_unicode
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import pytz
|
||||
import re
|
||||
import time as _time
|
||||
|
||||
|
||||
DATE_PART = r'(\d+)D'
|
||||
TIME_PART = r'T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?'
|
||||
DATETIME_PART = '(?:%s)?(?:%s)?' % (DATE_PART, TIME_PART)
|
||||
WEEKS_PART = r'(\d+)W'
|
||||
DURATION_REGEX = re.compile(r'([-+]?)P(?:%s|%s)$'
|
||||
% (WEEKS_PART, DATETIME_PART))
|
||||
WEEKDAY_RULE = re.compile('(?P<signal>[+-]?)(?P<relative>[\d]?)'
|
||||
'(?P<weekday>[\w]{2})$')
|
||||
|
||||
|
||||
####################################################
|
||||
# handy tzinfo classes you can use.
|
||||
#
|
||||
|
||||
ZERO = timedelta(0)
|
||||
HOUR = timedelta(hours=1)
|
||||
STDOFFSET = timedelta(seconds=-_time.timezone)
|
||||
if _time.daylight:
|
||||
DSTOFFSET = timedelta(seconds=-_time.altzone)
|
||||
else:
|
||||
DSTOFFSET = STDOFFSET
|
||||
DSTDIFF = DSTOFFSET - STDOFFSET
|
||||
|
||||
|
||||
class FixedOffset(tzinfo):
|
||||
"""Fixed offset in minutes east from UTC.
|
||||
"""
|
||||
def __init__(self, offset, name):
|
||||
self.__offset = timedelta(minutes=offset)
|
||||
self.__name = name
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self.__offset
|
||||
|
||||
def tzname(self, dt):
|
||||
return self.__name
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
|
||||
class LocalTimezone(tzinfo):
|
||||
"""Timezone of the machine where the code is running.
|
||||
"""
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return DSTOFFSET
|
||||
else:
|
||||
return STDOFFSET
|
||||
|
||||
def dst(self, dt):
|
||||
if self._isdst(dt):
|
||||
return DSTDIFF
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return _time.tzname[self._isdst(dt)]
|
||||
|
||||
def _isdst(self, dt):
|
||||
tt = (dt.year, dt.month, dt.day,
|
||||
dt.hour, dt.minute, dt.second,
|
||||
dt.weekday(), 0, -1)
|
||||
stamp = _time.mktime(tt)
|
||||
tt = _time.localtime(stamp)
|
||||
return tt.tm_isdst > 0
|
||||
|
||||
|
||||
class vBinary(object):
|
||||
"""Binary property values are base 64 encoded.
|
||||
"""
|
||||
|
||||
def __init__(self, obj):
|
||||
self.obj = to_unicode(obj)
|
||||
self.params = Parameters(encoding='BASE64', value="BINARY")
|
||||
|
||||
def __repr__(self):
|
||||
return "vBinary('%s')" % self.to_ical()
|
||||
|
||||
def to_ical(self):
|
||||
return binascii.b2a_base64(self.obj.encode('utf-8'))[:-1]
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical):
|
||||
try:
|
||||
return base64.b64decode(ical)
|
||||
except UnicodeError:
|
||||
raise ValueError('Not valid base 64 encoding.')
|
||||
|
||||
|
||||
class vBoolean(int):
|
||||
"""Returns specific string according to state.
|
||||
"""
|
||||
BOOL_MAP = CaselessDict(true=True, false=False)
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
self = super(vBoolean, cls).__new__(cls, *args, **kwargs)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
if self:
|
||||
return b'TRUE'
|
||||
return b'FALSE'
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
try:
|
||||
return cls.BOOL_MAP[ical]
|
||||
except:
|
||||
raise ValueError("Expected 'TRUE' or 'FALSE'. Got %s" % ical)
|
||||
|
||||
|
||||
class vCalAddress(compat.unicode_type):
|
||||
"""This just returns an unquoted string.
|
||||
"""
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
self = super(vCalAddress, cls).__new__(cls, value)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "vCalAddress('%s')" % self.to_ical()
|
||||
|
||||
def to_ical(self):
|
||||
return self.encode(DEFAULT_ENCODING)
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
return cls(ical)
|
||||
|
||||
|
||||
class vFloat(float):
|
||||
"""Just a float.
|
||||
"""
|
||||
def __new__(cls, *args, **kwargs):
|
||||
self = super(vFloat, cls).__new__(cls, *args, **kwargs)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
return compat.unicode_type(self).encode('utf-8')
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
try:
|
||||
return cls(ical)
|
||||
except:
|
||||
raise ValueError('Expected float value, got: %s' % ical)
|
||||
|
||||
|
||||
class vInt(int):
|
||||
"""Just an int.
|
||||
"""
|
||||
def __new__(cls, *args, **kwargs):
|
||||
self = super(vInt, cls).__new__(cls, *args, **kwargs)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
return compat.unicode_type(self).encode('utf-8')
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
try:
|
||||
return cls(ical)
|
||||
except:
|
||||
raise ValueError('Expected int, got: %s' % ical)
|
||||
|
||||
|
||||
class vDDDLists(object):
|
||||
"""A list of vDDDTypes values.
|
||||
"""
|
||||
def __init__(self, dt_list):
|
||||
if not hasattr(dt_list, '__iter__'):
|
||||
dt_list = [dt_list]
|
||||
vDDD = []
|
||||
tzid = None
|
||||
for dt in dt_list:
|
||||
dt = vDDDTypes(dt)
|
||||
vDDD.append(dt)
|
||||
if 'TZID' in dt.params:
|
||||
tzid = dt.params['TZID']
|
||||
|
||||
if tzid:
|
||||
# NOTE: no support for multiple timezones here!
|
||||
self.params = Parameters({'TZID': tzid})
|
||||
self.dts = vDDD
|
||||
|
||||
def to_ical(self):
|
||||
dts_ical = (dt.to_ical() for dt in self.dts)
|
||||
return b",".join(dts_ical)
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical, timezone=None):
|
||||
out = []
|
||||
ical_dates = ical.split(",")
|
||||
for ical_dt in ical_dates:
|
||||
out.append(vDDDTypes.from_ical(ical_dt, timezone=timezone))
|
||||
return out
|
||||
|
||||
|
||||
class vDDDTypes(object):
|
||||
"""A combined Datetime, Date or Duration parser/generator. Their format
|
||||
cannot be confused, and often values can be of either types.
|
||||
So this is practical.
|
||||
"""
|
||||
def __init__(self, dt):
|
||||
if not isinstance(dt, (datetime, date, timedelta, time)):
|
||||
raise ValueError('You must use datetime, date, timedelta or time')
|
||||
if isinstance(dt, datetime):
|
||||
self.params = Parameters(dict(value='DATE-TIME'))
|
||||
elif isinstance(dt, date):
|
||||
self.params = Parameters(dict(value='DATE'))
|
||||
elif isinstance(dt, time):
|
||||
self.params = Parameters(dict(value='TIME'))
|
||||
|
||||
if (isinstance(dt, datetime) or isinstance(dt, time))\
|
||||
and getattr(dt, 'tzinfo', False):
|
||||
tzinfo = dt.tzinfo
|
||||
if tzinfo is not pytz.utc and not isinstance(tzinfo, tzutc):
|
||||
# set the timezone as a parameter to the property
|
||||
tzid = tzid_from_dt(dt)
|
||||
if tzid:
|
||||
self.params.update({'TZID': tzid})
|
||||
self.dt = dt
|
||||
|
||||
def to_ical(self):
|
||||
dt = self.dt
|
||||
if isinstance(dt, datetime):
|
||||
return vDatetime(dt).to_ical()
|
||||
elif isinstance(dt, date):
|
||||
return vDate(dt).to_ical()
|
||||
elif isinstance(dt, timedelta):
|
||||
return vDuration(dt).to_ical()
|
||||
elif isinstance(dt, time):
|
||||
return vTime(dt).to_ical()
|
||||
else:
|
||||
raise ValueError('Unknown date type')
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical, timezone=None):
|
||||
if isinstance(ical, cls):
|
||||
return ical.dt
|
||||
u = ical.upper()
|
||||
if u.startswith('-P') or u.startswith('P'):
|
||||
return vDuration.from_ical(ical)
|
||||
try:
|
||||
return vDatetime.from_ical(ical, timezone=timezone)
|
||||
except ValueError:
|
||||
try:
|
||||
return vDate.from_ical(ical)
|
||||
except ValueError:
|
||||
return vTime.from_ical(ical)
|
||||
|
||||
|
||||
class vDate(object):
|
||||
"""Render and generates iCalendar date format.
|
||||
"""
|
||||
def __init__(self, dt):
|
||||
if not isinstance(dt, date):
|
||||
raise ValueError('Value MUST be a date instance')
|
||||
self.dt = dt
|
||||
self.params = Parameters(dict(value='DATE'))
|
||||
|
||||
def to_ical(self):
|
||||
s = "%04d%02d%02d" % (self.dt.year, self.dt.month, self.dt.day)
|
||||
return s.encode('utf-8')
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical):
|
||||
try:
|
||||
timetuple = (
|
||||
int(ical[:4]), # year
|
||||
int(ical[4:6]), # month
|
||||
int(ical[6:8]), # day
|
||||
)
|
||||
return date(*timetuple)
|
||||
except:
|
||||
raise ValueError('Wrong date format %s' % ical)
|
||||
|
||||
|
||||
class vDatetime(object):
|
||||
"""Render and generates icalendar datetime format.
|
||||
|
||||
vDatetime is timezone aware and uses the pytz library, an implementation of
|
||||
the Olson database in Python. When a vDatetime object is created from an
|
||||
ical string, you can pass a valid pytz timezone identifier. When a
|
||||
vDatetime object is created from a python datetime object, it uses the
|
||||
tzinfo component, if present. Otherwise an timezone-naive object is
|
||||
created. Be aware that there are certain limitations with timezone naive
|
||||
DATE-TIME components in the icalendar standard.
|
||||
"""
|
||||
def __init__(self, dt):
|
||||
self.dt = dt
|
||||
self.params = Parameters()
|
||||
|
||||
def to_ical(self):
|
||||
dt = self.dt
|
||||
tzid = tzid_from_dt(dt)
|
||||
|
||||
s = "%04d%02d%02dT%02d%02d%02d" % (
|
||||
dt.year,
|
||||
dt.month,
|
||||
dt.day,
|
||||
dt.hour,
|
||||
dt.minute,
|
||||
dt.second
|
||||
)
|
||||
if tzid == 'UTC':
|
||||
s += "Z"
|
||||
elif tzid:
|
||||
self.params.update({'TZID': tzid})
|
||||
return s.encode('utf-8')
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical, timezone=None):
|
||||
tzinfo = None
|
||||
if timezone:
|
||||
try:
|
||||
tzinfo = pytz.timezone(timezone)
|
||||
except pytz.UnknownTimeZoneError:
|
||||
pass
|
||||
|
||||
try:
|
||||
timetuple = (
|
||||
int(ical[:4]), # year
|
||||
int(ical[4:6]), # month
|
||||
int(ical[6:8]), # day
|
||||
int(ical[9:11]), # hour
|
||||
int(ical[11:13]), # minute
|
||||
int(ical[13:15]), # second
|
||||
)
|
||||
if tzinfo:
|
||||
return tzinfo.localize(datetime(*timetuple))
|
||||
elif not ical[15:]:
|
||||
return datetime(*timetuple)
|
||||
elif ical[15:16] == 'Z':
|
||||
return datetime(tzinfo=pytz.utc, *timetuple)
|
||||
else:
|
||||
raise ValueError(ical)
|
||||
except:
|
||||
raise ValueError('Wrong datetime format: %s' % ical)
|
||||
|
||||
|
||||
class vDuration(object):
|
||||
"""Subclass of timedelta that renders itself in the iCalendar DURATION
|
||||
format.
|
||||
"""
|
||||
|
||||
def __init__(self, td):
|
||||
if not isinstance(td, timedelta):
|
||||
raise ValueError('Value MUST be a timedelta instance')
|
||||
self.td = td
|
||||
self.params = Parameters()
|
||||
|
||||
def to_ical(self):
|
||||
sign = ""
|
||||
if self.td.days < 0:
|
||||
sign = "-"
|
||||
self.td = -self.td
|
||||
timepart = ""
|
||||
if self.td.seconds:
|
||||
timepart = "T"
|
||||
hours = self.td.seconds // 3600
|
||||
minutes = self.td.seconds % 3600 // 60
|
||||
seconds = self.td.seconds % 60
|
||||
if hours:
|
||||
timepart += "%dH" % hours
|
||||
if minutes or (hours and seconds):
|
||||
timepart += "%dM" % minutes
|
||||
if seconds:
|
||||
timepart += "%dS" % seconds
|
||||
if self.td.days == 0 and timepart:
|
||||
return (compat.unicode_type(sign).encode('utf-8') + b'P' +
|
||||
compat.unicode_type(timepart).encode('utf-8'))
|
||||
else:
|
||||
return (compat.unicode_type(sign).encode('utf-8') + b'P' +
|
||||
compat.unicode_type(abs(self.td.days)).encode('utf-8') +
|
||||
b'D' + compat.unicode_type(timepart).encode('utf-8'))
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical):
|
||||
try:
|
||||
match = DURATION_REGEX.match(ical)
|
||||
sign, weeks, days, hours, minutes, seconds = match.groups()
|
||||
if weeks:
|
||||
value = timedelta(weeks=int(weeks))
|
||||
else:
|
||||
value = timedelta(days=int(days or 0),
|
||||
hours=int(hours or 0),
|
||||
minutes=int(minutes or 0),
|
||||
seconds=int(seconds or 0))
|
||||
if sign == '-':
|
||||
value = -value
|
||||
return value
|
||||
except:
|
||||
raise ValueError('Invalid iCalendar duration: %s' % ical)
|
||||
|
||||
|
||||
class vPeriod(object):
|
||||
"""A precise period of time.
|
||||
"""
|
||||
def __init__(self, per):
|
||||
start, end_or_duration = per
|
||||
if not (isinstance(start, datetime) or isinstance(start, date)):
|
||||
raise ValueError('Start value MUST be a datetime or date instance')
|
||||
if not (isinstance(end_or_duration, datetime) or
|
||||
isinstance(end_or_duration, date) or
|
||||
isinstance(end_or_duration, timedelta)):
|
||||
raise ValueError('end_or_duration MUST be a datetime, '
|
||||
'date or timedelta instance')
|
||||
by_duration = 0
|
||||
if isinstance(end_or_duration, timedelta):
|
||||
by_duration = 1
|
||||
duration = end_or_duration
|
||||
end = start + duration
|
||||
else:
|
||||
end = end_or_duration
|
||||
duration = end - start
|
||||
if start > end:
|
||||
raise ValueError("Start time is greater than end time")
|
||||
|
||||
self.params = Parameters()
|
||||
# set the timezone identifier
|
||||
# does not support different timezones for start and end
|
||||
tzid = tzid_from_dt(start)
|
||||
if tzid:
|
||||
self.params['TZID'] = tzid
|
||||
|
||||
self.start = start
|
||||
self.end = end
|
||||
self.by_duration = by_duration
|
||||
self.duration = duration
|
||||
|
||||
def __cmp__(self, other):
|
||||
if not isinstance(other, vPeriod):
|
||||
raise NotImplementedError('Cannot compare vPeriod with %r' % other)
|
||||
return cmp((self.start, self.end), (other.start, other.end))
|
||||
|
||||
def overlaps(self, other):
|
||||
if self.start > other.start:
|
||||
return other.overlaps(self)
|
||||
if self.start <= other.start < self.end:
|
||||
return True
|
||||
return False
|
||||
|
||||
def to_ical(self):
|
||||
if self.by_duration:
|
||||
return (vDatetime(self.start).to_ical() + b'/' +
|
||||
vDuration(self.duration).to_ical())
|
||||
return (vDatetime(self.start).to_ical() + b'/' +
|
||||
vDatetime(self.end).to_ical())
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical):
|
||||
try:
|
||||
start, end_or_duration = ical.split('/')
|
||||
start = vDDDTypes.from_ical(start)
|
||||
end_or_duration = vDDDTypes.from_ical(end_or_duration)
|
||||
return (start, end_or_duration)
|
||||
except:
|
||||
raise ValueError('Expected period format, got: %s' % ical)
|
||||
|
||||
def __repr__(self):
|
||||
if self.by_duration:
|
||||
p = (self.start, self.duration)
|
||||
else:
|
||||
p = (self.start, self.end)
|
||||
return 'vPeriod(%r)' % p
|
||||
|
||||
|
||||
class vWeekday(compat.unicode_type):
|
||||
"""This returns an unquoted weekday abbrevation.
|
||||
"""
|
||||
week_days = CaselessDict({
|
||||
"SU": 0, "MO": 1, "TU": 2, "WE": 3, "TH": 4, "FR": 5, "SA": 6,
|
||||
})
|
||||
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
self = super(vWeekday, cls).__new__(cls, value)
|
||||
match = WEEKDAY_RULE.match(self)
|
||||
if match is None:
|
||||
raise ValueError('Expected weekday abbrevation, got: %s' % self)
|
||||
match = match.groupdict()
|
||||
sign = match['signal']
|
||||
weekday = match['weekday']
|
||||
relative = match['relative']
|
||||
if not weekday in vWeekday.week_days or sign not in '+-':
|
||||
raise ValueError('Expected weekday abbrevation, got: %s' % self)
|
||||
self.relative = relative and int(relative) or None
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
return self.encode(DEFAULT_ENCODING).upper()
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
try:
|
||||
return cls(ical.upper())
|
||||
except:
|
||||
raise ValueError('Expected weekday abbrevation, got: %s' % ical)
|
||||
|
||||
|
||||
class vFrequency(compat.unicode_type):
|
||||
"""A simple class that catches illegal values.
|
||||
"""
|
||||
|
||||
frequencies = CaselessDict({
|
||||
"SECONDLY": "SECONDLY",
|
||||
"MINUTELY": "MINUTELY",
|
||||
"HOURLY": "HOURLY",
|
||||
"DAILY": "DAILY",
|
||||
"WEEKLY": "WEEKLY",
|
||||
"MONTHLY": "MONTHLY",
|
||||
"YEARLY": "YEARLY",
|
||||
})
|
||||
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
self = super(vFrequency, cls).__new__(cls, value)
|
||||
if not self in vFrequency.frequencies:
|
||||
raise ValueError('Expected frequency, got: %s' % self)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
return self.encode(DEFAULT_ENCODING).upper()
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
try:
|
||||
return cls(ical.upper())
|
||||
except:
|
||||
raise ValueError('Expected frequency, got: %s' % ical)
|
||||
|
||||
|
||||
class vRecur(CaselessDict):
|
||||
"""Recurrence definition.
|
||||
"""
|
||||
|
||||
frequencies = ["SECONDLY", "MINUTELY", "HOURLY", "DAILY", "WEEKLY",
|
||||
"MONTHLY", "YEARLY"]
|
||||
|
||||
# Mac iCal ignores RRULEs where FREQ is not the first rule part.
|
||||
# Sorts parts according to the order listed in RFC 5545, section 3.3.10.
|
||||
canonical_order = ("FREQ", "UNTIL", "COUNT", "INTERVAL",
|
||||
"BYSECOND", "BYMINUTE", "BYHOUR", "BYDAY",
|
||||
"BYMONTHDAY", "BYYEARDAY", "BYWEEKNO", "BYMONTH",
|
||||
"BYSETPOS", "WKST")
|
||||
|
||||
types = CaselessDict({
|
||||
'COUNT': vInt,
|
||||
'INTERVAL': vInt,
|
||||
'BYSECOND': vInt,
|
||||
'BYMINUTE': vInt,
|
||||
'BYHOUR': vInt,
|
||||
'BYMONTHDAY': vInt,
|
||||
'BYYEARDAY': vInt,
|
||||
'BYMONTH': vInt,
|
||||
'UNTIL': vDDDTypes,
|
||||
'BYSETPOS': vInt,
|
||||
'WKST': vWeekday,
|
||||
'BYDAY': vWeekday,
|
||||
'FREQ': vFrequency,
|
||||
})
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
CaselessDict.__init__(self, *args, **kwargs)
|
||||
self.params = Parameters()
|
||||
|
||||
def to_ical(self):
|
||||
result = []
|
||||
for key, vals in self.sorted_items():
|
||||
typ = self.types[key]
|
||||
if not isinstance(vals, SEQUENCE_TYPES):
|
||||
vals = [vals]
|
||||
vals = b','.join(typ(val).to_ical() for val in vals)
|
||||
|
||||
# CaselessDict keys are always unicode
|
||||
key = key.encode(DEFAULT_ENCODING)
|
||||
result.append(key + b'=' + vals)
|
||||
|
||||
return b';'.join(result)
|
||||
|
||||
@classmethod
|
||||
def parse_type(cls, key, values):
|
||||
# integers
|
||||
parser = cls.types.get(key, vText)
|
||||
return [parser.from_ical(v) for v in values.split(',')]
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
if isinstance(ical, cls):
|
||||
return ical
|
||||
try:
|
||||
recur = cls()
|
||||
for pairs in ical.split(';'):
|
||||
key, vals = pairs.split('=')
|
||||
recur[key] = cls.parse_type(key, vals)
|
||||
return dict(recur)
|
||||
except:
|
||||
raise ValueError('Error in recurrence rule: %s' % ical)
|
||||
|
||||
|
||||
class vText(compat.unicode_type):
|
||||
"""Simple text.
|
||||
"""
|
||||
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
self = super(vText, cls).__new__(cls, value)
|
||||
self.encoding = encoding
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "vText('%s')" % self.to_ical()
|
||||
|
||||
def to_ical(self):
|
||||
return escape_char(self).encode(self.encoding)
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
ical_unesc = unescape_char(ical)
|
||||
return cls(ical_unesc)
|
||||
|
||||
|
||||
class vTime(object):
|
||||
"""Render and generates iCalendar time format.
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
if len(args) == 1:
|
||||
if not isinstance(args[0], (time, datetime)):
|
||||
raise ValueError('Expected a datetime.time, got: %s' % args[0])
|
||||
self.dt = args[0]
|
||||
else:
|
||||
self.dt = time(*args)
|
||||
self.params = Parameters(dict(value='TIME'))
|
||||
|
||||
def to_ical(self):
|
||||
return self.dt.strftime("%H%M%S")
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical):
|
||||
# TODO: timezone support
|
||||
try:
|
||||
timetuple = (int(ical[:2]), int(ical[2:4]), int(ical[4:6]))
|
||||
return time(*timetuple)
|
||||
except:
|
||||
raise ValueError('Expected time, got: %s' % ical)
|
||||
|
||||
|
||||
class vUri(compat.unicode_type):
|
||||
"""Uniform resource identifier is basically just an unquoted string.
|
||||
"""
|
||||
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
self = super(vUri, cls).__new__(cls, value)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
return self.encode(DEFAULT_ENCODING)
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
try:
|
||||
return cls(ical)
|
||||
except:
|
||||
raise ValueError('Expected , got: %s' % ical)
|
||||
|
||||
|
||||
class vGeo(object):
|
||||
"""A special type that is only indirectly defined in the rfc.
|
||||
"""
|
||||
|
||||
def __init__(self, geo):
|
||||
try:
|
||||
latitude, longitude = (geo[0], geo[1])
|
||||
latitude = float(latitude)
|
||||
longitude = float(longitude)
|
||||
except:
|
||||
raise ValueError('Input must be (float, float) for '
|
||||
'latitude and longitude')
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.params = Parameters()
|
||||
|
||||
def to_ical(self):
|
||||
return '%s;%s' % (self.latitude, self.longitude)
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical):
|
||||
try:
|
||||
latitude, longitude = ical.split(';')
|
||||
return (float(latitude), float(longitude))
|
||||
except:
|
||||
raise ValueError("Expected 'float;float' , got: %s" % ical)
|
||||
|
||||
|
||||
class vUTCOffset(object):
|
||||
"""Renders itself as a utc offset.
|
||||
"""
|
||||
|
||||
def __init__(self, td):
|
||||
if not isinstance(td, timedelta):
|
||||
raise ValueError('Offset value MUST be a timedelta instance')
|
||||
self.td = td
|
||||
self.params = Parameters()
|
||||
|
||||
def to_ical(self):
|
||||
|
||||
if self.td < timedelta(0):
|
||||
sign = '-%s'
|
||||
td = timedelta(0)-self.td # get timedelta relative to 0
|
||||
else:
|
||||
# Google Calendar rejects '0000' but accepts '+0000'
|
||||
sign = '+%s'
|
||||
td = self.td
|
||||
|
||||
days, seconds = td.days, td.seconds
|
||||
|
||||
hours = abs(days * 24 + seconds // 3600)
|
||||
minutes = abs((seconds % 3600) // 60)
|
||||
seconds = abs(seconds % 60)
|
||||
if seconds:
|
||||
duration = '%02i%02i%02i' % (hours, minutes, seconds)
|
||||
else:
|
||||
duration = '%02i%02i' % (hours, minutes)
|
||||
return sign % duration
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
if isinstance(ical, cls):
|
||||
return ical.td
|
||||
try:
|
||||
sign, hours, minutes, seconds = (ical[0:1],
|
||||
int(ical[1:3]),
|
||||
int(ical[3:5]),
|
||||
int(ical[5:7] or 0))
|
||||
offset = timedelta(hours=hours, minutes=minutes, seconds=seconds)
|
||||
except:
|
||||
raise ValueError('Expected utc offset, got: %s' % ical)
|
||||
if offset >= timedelta(hours=24):
|
||||
raise ValueError(
|
||||
'Offset must be less than 24 hours, was %s' % ical)
|
||||
if sign == '-':
|
||||
return -offset
|
||||
return offset
|
||||
|
||||
|
||||
class vInline(compat.unicode_type):
|
||||
"""This is an especially dumb class that just holds raw unparsed text and
|
||||
has parameters. Conversion of inline values are handled by the Component
|
||||
class, so no further processing is needed.
|
||||
"""
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
self = super(vInline, cls).__new__(cls, value)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
return self.encode(DEFAULT_ENCODING)
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
return cls(ical)
|
||||
|
||||
|
||||
class TypesFactory(CaselessDict):
|
||||
"""All Value types defined in rfc 2445 are registered in this factory
|
||||
class.
|
||||
|
||||
The value and parameter names don't overlap. So one factory is enough for
|
||||
both kinds.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Set keys to upper for initial dict"
|
||||
CaselessDict.__init__(self, *args, **kwargs)
|
||||
self.all_types = (
|
||||
vBinary,
|
||||
vBoolean,
|
||||
vCalAddress,
|
||||
vDDDLists,
|
||||
vDDDTypes,
|
||||
vDate,
|
||||
vDatetime,
|
||||
vDuration,
|
||||
vFloat,
|
||||
vFrequency,
|
||||
vGeo,
|
||||
vInline,
|
||||
vInt,
|
||||
vPeriod,
|
||||
vRecur,
|
||||
vText,
|
||||
vTime,
|
||||
vUTCOffset,
|
||||
vUri,
|
||||
vWeekday
|
||||
)
|
||||
self['binary'] = vBinary
|
||||
self['boolean'] = vBoolean
|
||||
self['cal-address'] = vCalAddress
|
||||
self['date'] = vDDDTypes
|
||||
self['date-time'] = vDDDTypes
|
||||
self['duration'] = vDDDTypes
|
||||
self['float'] = vFloat
|
||||
self['integer'] = vInt
|
||||
self['period'] = vPeriod
|
||||
self['recur'] = vRecur
|
||||
self['text'] = vText
|
||||
self['time'] = vTime
|
||||
self['uri'] = vUri
|
||||
self['utc-offset'] = vUTCOffset
|
||||
self['geo'] = vGeo
|
||||
self['inline'] = vInline
|
||||
self['date-time-list'] = vDDDLists
|
||||
|
||||
#################################################
|
||||
# Property types
|
||||
|
||||
# These are the default types
|
||||
types_map = CaselessDict({
|
||||
####################################
|
||||
# Property value types
|
||||
# Calendar Properties
|
||||
'calscale': 'text',
|
||||
'method': 'text',
|
||||
'prodid': 'text',
|
||||
'version': 'text',
|
||||
# Descriptive Component Properties
|
||||
'attach': 'uri',
|
||||
'categories': 'text',
|
||||
'class': 'text',
|
||||
'comment': 'text',
|
||||
'description': 'text',
|
||||
'geo': 'geo',
|
||||
'location': 'text',
|
||||
'percent-complete': 'integer',
|
||||
'priority': 'integer',
|
||||
'resources': 'text',
|
||||
'status': 'text',
|
||||
'summary': 'text',
|
||||
# Date and Time Component Properties
|
||||
'completed': 'date-time',
|
||||
'dtend': 'date-time',
|
||||
'due': 'date-time',
|
||||
'dtstart': 'date-time',
|
||||
'duration': 'duration',
|
||||
'freebusy': 'period',
|
||||
'transp': 'text',
|
||||
# Time Zone Component Properties
|
||||
'tzid': 'text',
|
||||
'tzname': 'text',
|
||||
'tzoffsetfrom': 'utc-offset',
|
||||
'tzoffsetto': 'utc-offset',
|
||||
'tzurl': 'uri',
|
||||
# Relationship Component Properties
|
||||
'attendee': 'cal-address',
|
||||
'contact': 'text',
|
||||
'organizer': 'cal-address',
|
||||
'recurrence-id': 'date-time',
|
||||
'related-to': 'text',
|
||||
'url': 'uri',
|
||||
'uid': 'text',
|
||||
# Recurrence Component Properties
|
||||
'exdate': 'date-time-list',
|
||||
'exrule': 'recur',
|
||||
'rdate': 'date-time-list',
|
||||
'rrule': 'recur',
|
||||
# Alarm Component Properties
|
||||
'action': 'text',
|
||||
'repeat': 'integer',
|
||||
'trigger': 'duration',
|
||||
# Change Management Component Properties
|
||||
'created': 'date-time',
|
||||
'dtstamp': 'date-time',
|
||||
'last-modified': 'date-time',
|
||||
'sequence': 'integer',
|
||||
# Miscellaneous Component Properties
|
||||
'request-status': 'text',
|
||||
####################################
|
||||
# parameter types (luckily there is no name overlap)
|
||||
'altrep': 'uri',
|
||||
'cn': 'text',
|
||||
'cutype': 'text',
|
||||
'delegated-from': 'cal-address',
|
||||
'delegated-to': 'cal-address',
|
||||
'dir': 'uri',
|
||||
'encoding': 'text',
|
||||
'fmttype': 'text',
|
||||
'fbtype': 'text',
|
||||
'language': 'text',
|
||||
'member': 'cal-address',
|
||||
'partstat': 'text',
|
||||
'range': 'text',
|
||||
'related': 'text',
|
||||
'reltype': 'text',
|
||||
'role': 'text',
|
||||
'rsvp': 'boolean',
|
||||
'sent-by': 'cal-address',
|
||||
'tzid': 'text',
|
||||
'value': 'text',
|
||||
})
|
||||
|
||||
def for_property(self, name):
|
||||
"""Returns a the default type for a property or parameter
|
||||
"""
|
||||
return self[self.types_map.get(name, 'text')]
|
||||
|
||||
def to_ical(self, name, value):
|
||||
"""Encodes a named value from a primitive python type to an icalendar
|
||||
encoded string.
|
||||
"""
|
||||
type_class = self.for_property(name)
|
||||
return type_class(value).to_ical()
|
||||
|
||||
def from_ical(self, name, value):
|
||||
"""Decodes a named property or parameter value from an icalendar
|
||||
encoded string to a primitive python type.
|
||||
"""
|
||||
type_class = self.for_property(name)
|
||||
decoded = type_class.from_ical(value)
|
||||
return decoded
|
@ -0,0 +1,5 @@
|
||||
# unittest/unittest2 importer
|
||||
import unittest
|
||||
if not hasattr(unittest.TestCase, 'assertIsNotNone'):
|
||||
import unittest2 as unittest
|
||||
unittest # pep 8
|
16
libs/icalendar-3.6.1/build/lib/icalendar/tests/encoding.ics
Normal file
16
libs/icalendar-3.6.1/build/lib/icalendar/tests/encoding.ics
Normal file
@ -0,0 +1,16 @@
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Plönë.org//NONSGML plone.app.event//EN
|
||||
VERSION:2.0
|
||||
X-WR-CALNAME:äöü ÄÖÜ €
|
||||
X-WR-CALDESC:test non ascii: äöü ÄÖÜ €
|
||||
X-WR-RELCALID:12345
|
||||
BEGIN:VEVENT
|
||||
DTSTART:20101010T100000Z
|
||||
DTEND:20101010T120000Z
|
||||
CREATED:20101010T100000Z
|
||||
UID:123456
|
||||
SUMMARY:Non-ASCII Test: ÄÖÜ äöü €
|
||||
DESCRIPTION:icalendar should be able to handle non-ascii: €äüöÄÜÖ.
|
||||
LOCATION:Tribstrül
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
@ -0,0 +1,48 @@
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Google Inc//Google Calendar 70.9054//EN
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
X-WR-CALNAME:Market East
|
||||
X-WR-TIMEZONE:America/New_York
|
||||
X-WR-CALDESC:
|
||||
BEGIN:VTIMEZONE
|
||||
TZID: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
|
||||
DTSTART;TZID=America/New_York:20130907T120000
|
||||
DTEND;TZID=America/New_York:20130907T170000
|
||||
RRULE:FREQ=WEEKLY;BYDAY=FR,SA;UNTIL=20131025T035959Z
|
||||
EXDATE;TZID=America/New_York:20131012T120000
|
||||
EXDATE;TZID=America/New_York:20131011T120000
|
||||
DTSTAMP:20131021T025552Z
|
||||
UID:ak30b02u7858q1oo6ji9dm4mgg@google.com
|
||||
CREATED:20130903T181453Z
|
||||
DESCRIPTION:The Fieldhouse and Hard Rock Cafe are working with PhillyRising
|
||||
to provide live entertainment on Friday and Saturday afternoons throughout
|
||||
the Summer.
|
||||
LAST-MODIFIED:20131015T210927Z
|
||||
LOCATION:12th and Market Streets (weather permitting)
|
||||
SEQUENCE:0
|
||||
STATUS:CONFIRMED
|
||||
SUMMARY:Market East Live!
|
||||
TRANSP:OPAQUE
|
||||
END:VEVENT
|
||||
|
||||
END:VCALENDAR
|
@ -0,0 +1,41 @@
|
||||
BEGIN:VEVENT
|
||||
DTSTART:20130927T130000Z
|
||||
DTEND:20130927T140000Z
|
||||
DTSTAMP:20131107T004757Z
|
||||
ORGANIZER;CN=gxxxxxxxn@nxx.fr:mailto:gxxxxxn@nxx.fr
|
||||
UID:040000008200E00074C5B7101A82E00800000000A0F3321606B6CE01000000000000000
|
||||
010000000F09F33F0E8ED4C44B99F6027ACF588D0
|
||||
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=St
|
||||
eve Bxxxxxx;X-NUM-GUESTS=0:mailto:sxxxxxt@nxx.fr
|
||||
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Boris
|
||||
Hxxxxx;X-NUM-GUESTS=0:mailto:bxxxxxxk@vxxxxxxxx.com
|
||||
CREATED:20130920T113409Z
|
||||
DESCRIPTION:Quand : vendredi 27 septembre 2013 15:00-16:00 (UTC+01:00) Brux
|
||||
elles\, Copenhague\, Madrid\, Paris.\nEmplacement : Conf-Call - 01 xx xx xx
|
||||
xx\n\nRemarque : le décalage GMT ci-dessus ne tient pas compte des réglage
|
||||
s de l'heure d'été.\n\n*~*~*~*~*~*~*~*~*~*\n\nComme convenu à l’instant par
|
||||
e-mail\n
|
||||
LAST-MODIFIED:20130920T115104Z
|
||||
LOCATION:Conf-Call - 01 xx xx xx xx
|
||||
SEQUENCE:0
|
||||
STATUS:CONFIRMED
|
||||
SUMMARY:Nxx - Réunion lancement PxxxxxxT
|
||||
TRANSP:OPAQUE
|
||||
X-ALT-DESC;FMTTYPE=text/html:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//E
|
||||
N">\n<HTML>\n<HEAD>\n<META NAME="Generator" CONTENT="MS Exchange Server ver
|
||||
sion 08.00.0681.000">\n<TITLE></TITLE>\n</HEAD>\n<BODY>\n<!-- Converted fro
|
||||
m text/rtf format -->\n\n<P DIR=LTR><SPAN LANG="fr"><FONT FACE="Calibri">Qu
|
||||
and : vendredi 27 septembre 2013 15:00-16:00 (UTC+01:00) Bruxelles\, Copenh
|
||||
ague\, Madrid\, Paris.</FONT></SPAN></P>\n\n<P DIR=LTR><SPAN LANG="fr"><FON
|
||||
T FACE="Calibri">Emplacement : Conf-Call - 01 xx xx xx xx</FONT></SPAN></P>
|
||||
\n\n<P DIR=LTR><SPAN LANG="fr"><FONT FACE="Calibri">Remarque : le décalage
|
||||
GMT ci-dessus ne tient pas compte des réglages de l'heure d'été.</FONT></SP
|
||||
AN></P>\n\n<P DIR=LTR><SPAN LANG="fr"><FONT FACE="Calibri">*~*~*~*~*~*~*~*~
|
||||
*~*</FONT></SPAN></P>\n\n<P DIR=LTR><SPAN LANG="fr"><FONT FACE="Calibri">Co
|
||||
mme convenu à l’instant par e-mail</FONT></SPAN><SPAN LANG="fr"></SPAN></P>
|
||||
\n\n</BODY>\n</HTML>
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
|
||||
X
|
||||
END:VEVENT
|
@ -0,0 +1,78 @@
|
||||
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
|
80
libs/icalendar-3.6.1/build/lib/icalendar/tests/multiple.ics
Normal file
80
libs/icalendar-3.6.1/build/lib/icalendar/tests/multiple.ics
Normal file
@ -0,0 +1,80 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION
|
||||
|
||||
:2.0
|
||||
PRODID
|
||||
|
||||
:-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN
|
||||
METHOD
|
||||
|
||||
:PUBLISH
|
||||
BEGIN:VEVENT
|
||||
UID
|
||||
|
||||
:956630271
|
||||
SUMMARY
|
||||
|
||||
:Christmas Day
|
||||
CLASS
|
||||
|
||||
:PUBLIC
|
||||
X-MOZILLA-ALARM-DEFAULT-UNITS
|
||||
|
||||
:minutes
|
||||
X-MOZILLA-ALARM-DEFAULT-LENGTH
|
||||
|
||||
:15
|
||||
X-MOZILLA-RECUR-DEFAULT-UNITS
|
||||
|
||||
:weeks
|
||||
X-MOZILLA-RECUR-DEFAULT-INTERVAL
|
||||
|
||||
:1
|
||||
DTSTART
|
||||
|
||||
;VALUE=DATE
|
||||
:20031225
|
||||
DTEND
|
||||
|
||||
;VALUE=DATE
|
||||
:20031226
|
||||
DTSTAMP
|
||||
|
||||
:20020430T114937Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
BEGIN:VCALENDAR
|
||||
VERSION
|
||||
:2.0
|
||||
PRODID
|
||||
:-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN
|
||||
METHOD
|
||||
:PUBLISH
|
||||
BEGIN:VEVENT
|
||||
UID
|
||||
:911737808
|
||||
SUMMARY
|
||||
:Boxing Day
|
||||
CLASS
|
||||
:PUBLIC
|
||||
X-MOZILLA-ALARM-DEFAULT-UNITS
|
||||
:minutes
|
||||
X-MOZILLA-ALARM-DEFAULT-LENGTH
|
||||
:15
|
||||
X-MOZILLA-RECUR-DEFAULT-UNITS
|
||||
:weeks
|
||||
X-MOZILLA-RECUR-DEFAULT-INTERVAL
|
||||
:1
|
||||
DTSTART
|
||||
;VALUE=DATE
|
||||
:20030501
|
||||
DTSTAMP
|
||||
:20020430T114937Z
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID
|
||||
:wh4t3v3r
|
||||
DTSTART;VALUE=DATE:20031225
|
||||
SUMMARY:Christmas again!
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
@ -0,0 +1,24 @@
|
||||
BEGIN:VCALENDAR
|
||||
METHOD:Request
|
||||
PRODID:-//My product//mxm.dk/
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
DTSTART:19960401T010000
|
||||
DTEND:19960401T020000
|
||||
RRULE:FREQ=DAILY;COUNT=100
|
||||
EXDATE:19960402T010000Z,19960403T010000Z,19960404T010000Z
|
||||
SUMMARY:A recurring event with exdates
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTART;TZID=Europe/Vienna:20120327T100000
|
||||
DTEND;TZID=Europe/Vienna:20120327T180000
|
||||
RRULE:FREQ=WEEKLY;UNTIL=20120703T080000Z;BYDAY=TU
|
||||
EXDATE;TZID=Europe/Vienna:20120529T100000
|
||||
EXDATE;TZID=Europe/Vienna:20120403T100000
|
||||
EXDATE;TZID=Europe/Vienna:20120410T100000
|
||||
EXDATE;TZID=Europe/Vienna:20120501T100000
|
||||
EXDATE;TZID=Europe/Vienna:20120417T100000
|
||||
DTSTAMP:20130716T120638Z
|
||||
SUMMARY:A Recurring event with multiple exdates, one per line.
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
@ -0,0 +1,90 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import datetime
|
||||
import icalendar
|
||||
import os
|
||||
import pytz
|
||||
|
||||
|
||||
class TestEncoding(unittest.TestCase):
|
||||
|
||||
def test_create_from_ical(self):
|
||||
directory = os.path.dirname(__file__)
|
||||
data = open(os.path.join(directory, 'encoding.ics'), 'rb').read()
|
||||
cal = icalendar.Calendar.from_ical(data)
|
||||
|
||||
self.assertEqual(cal['prodid'].to_ical().decode('utf-8'),
|
||||
u"-//Plönë.org//NONSGML plone.app.event//EN")
|
||||
self.assertEqual(cal['X-WR-CALDESC'].to_ical().decode('utf-8'),
|
||||
u"test non ascii: äöü ÄÖÜ €")
|
||||
|
||||
event = cal.walk('VEVENT')[0]
|
||||
self.assertEqual(event['SUMMARY'].to_ical().decode('utf-8'),
|
||||
u'Non-ASCII Test: ÄÖÜ äöü €')
|
||||
self.assertEqual(
|
||||
event['DESCRIPTION'].to_ical().decode('utf-8'),
|
||||
u'icalendar should be able to handle non-ascii: €äüöÄÜÖ.'
|
||||
)
|
||||
self.assertEqual(event['LOCATION'].to_ical().decode('utf-8'),
|
||||
u'Tribstrül')
|
||||
|
||||
def test_create_to_ical(self):
|
||||
cal = icalendar.Calendar()
|
||||
|
||||
cal.add('prodid', u"-//Plönë.org//NONSGML plone.app.event//EN")
|
||||
cal.add('version', u"2.0")
|
||||
cal.add('x-wr-calname', u"äöü ÄÖÜ €")
|
||||
cal.add('x-wr-caldesc', u"test non ascii: äöü ÄÖÜ €")
|
||||
cal.add('x-wr-relcalid', u"12345")
|
||||
|
||||
event = icalendar.Event()
|
||||
event.add(
|
||||
'dtstart',
|
||||
datetime.datetime(2010, 10, 10, 10, 00, 00, tzinfo=pytz.utc)
|
||||
)
|
||||
event.add(
|
||||
'dtend',
|
||||
datetime.datetime(2010, 10, 10, 12, 00, 00, tzinfo=pytz.utc)
|
||||
)
|
||||
event.add(
|
||||
'created',
|
||||
datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=pytz.utc)
|
||||
)
|
||||
event.add('uid', u'123456')
|
||||
event.add('summary', u'Non-ASCII Test: ÄÖÜ äöü €')
|
||||
event.add(
|
||||
'description',
|
||||
u'icalendar should be able to de/serialize non-ascii.'
|
||||
)
|
||||
event.add('location', u'Tribstrül')
|
||||
cal.add_component(event)
|
||||
|
||||
ical_lines = cal.to_ical().splitlines()
|
||||
cmp = b'PRODID:-//Pl\xc3\xb6n\xc3\xab.org//NONSGML plone.app.event//EN'
|
||||
self.assertTrue(cmp in ical_lines)
|
||||
|
||||
def test_create_event_simple(self):
|
||||
event = icalendar.Event()
|
||||
event.add(
|
||||
"dtstart",
|
||||
datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=pytz.utc)
|
||||
)
|
||||
event.add("summary", u"åäö")
|
||||
out = event.to_ical()
|
||||
summary = b'SUMMARY:\xc3\xa5\xc3\xa4\xc3\xb6'
|
||||
self.assertTrue(summary in out.splitlines())
|
||||
|
||||
def test_unicode_parameter_name(self):
|
||||
# Test for issue #80
|
||||
cal = icalendar.Calendar()
|
||||
event = icalendar.Event()
|
||||
event.add(u'DESCRIPTION', u'äöüßÄÖÜ')
|
||||
cal.add_component(event)
|
||||
c = cal.to_ical()
|
||||
self.assertEqual(
|
||||
c,
|
||||
b'BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDESCRIPTION:'
|
||||
+ b'\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f\xc3\x84\xc3\x96\xc3\x9c\r\n'
|
||||
+ b'END:VEVENT\r\nEND:VCALENDAR\r\n'
|
||||
)
|
@ -0,0 +1,247 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from icalendar.parser_tools import to_unicode
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import datetime
|
||||
import icalendar
|
||||
import os
|
||||
import pytz
|
||||
|
||||
|
||||
class TestIssues(unittest.TestCase):
|
||||
|
||||
def test_issue_53(self):
|
||||
"""Issue #53 - Parsing failure on some descriptions?
|
||||
https://github.com/collective/icalendar/issues/53
|
||||
"""
|
||||
|
||||
directory = os.path.dirname(__file__)
|
||||
ics = open(os.path.join(directory, 'issue_53_parsing_failure.ics'),
|
||||
'rb')
|
||||
cal = icalendar.Calendar.from_ical(ics.read())
|
||||
ics.close()
|
||||
|
||||
event = cal.walk('VEVENT')[0]
|
||||
desc = event.get('DESCRIPTION')
|
||||
self.assertTrue(b'July 12 at 6:30 PM' in desc.to_ical())
|
||||
|
||||
timezones = cal.walk('VTIMEZONE')
|
||||
self.assertEqual(len(timezones), 1)
|
||||
tz = timezones[0]
|
||||
self.assertEqual(tz['tzid'].to_ical(), b"America/New_York")
|
||||
|
||||
def test_issue_55(self):
|
||||
"""Issue #55 - Parse error on utc-offset with seconds value
|
||||
https://github.com/collective/icalendar/issues/55
|
||||
"""
|
||||
ical_str = """BEGIN:VTIMEZONE
|
||||
TZID:America/Los Angeles
|
||||
BEGIN:STANDARD
|
||||
DTSTART:18831118T120702
|
||||
RDATE:18831118T120702
|
||||
TZNAME:PST
|
||||
TZOFFSETFROM:-075258
|
||||
TZOFFSETTO:-0800
|
||||
END:STANDARD
|
||||
END:VTIMEZONE"""
|
||||
|
||||
tz = icalendar.Timezone.from_ical(ical_str)
|
||||
self.assertEqual(
|
||||
tz.to_ical(),
|
||||
b'BEGIN:VTIMEZONE\r\nTZID:America/Los Angeles\r\n'
|
||||
b'BEGIN:STANDARD\r\n'
|
||||
b'DTSTART:18831118T120702\r\nRDATE:18831118T120702\r\nTZNAME:PST'
|
||||
b'\r\nTZOFFSETFROM:-075258\r\nTZOFFSETTO:-0800\r\n'
|
||||
b'END:STANDARD\r\n'
|
||||
b'END:VTIMEZONE\r\n')
|
||||
|
||||
def test_issue_58(self):
|
||||
"""Issue #58 - TZID on UTC DATE-TIMEs
|
||||
https://github.com/collective/icalendar/issues/58
|
||||
"""
|
||||
|
||||
# According to RFC 2445: "The TZID property parameter MUST NOT be
|
||||
# applied to DATE-TIME or TIME properties whose time values are
|
||||
# specified in UTC."
|
||||
|
||||
event = icalendar.Event()
|
||||
dt = pytz.utc.localize(datetime.datetime(2012, 7, 16, 0, 0, 0))
|
||||
event.add('dtstart', dt)
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
b"BEGIN:VEVENT\r\n"
|
||||
b"DTSTART;VALUE=DATE-TIME:20120716T000000Z\r\n"
|
||||
b"END:VEVENT\r\n"
|
||||
)
|
||||
|
||||
def test_issue_64(self):
|
||||
"""Issue #64 - Event.to_ical() fails for unicode strings
|
||||
https://github.com/collective/icalendar/issues/64
|
||||
"""
|
||||
|
||||
# Non-unicode characters
|
||||
event = icalendar.Event()
|
||||
event.add("dtstart", datetime.datetime(2012, 9, 3, 0, 0, 0))
|
||||
event.add("summary", u"abcdef")
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:abcdef\r\nDTSTART;VALUE=DATE-TIME:"
|
||||
b"20120903T000000\r\nEND:VEVENT\r\n"
|
||||
)
|
||||
|
||||
# Unicode characters
|
||||
event = icalendar.Event()
|
||||
event.add("dtstart", datetime.datetime(2012, 9, 3, 0, 0, 0))
|
||||
event.add("summary", u"åäö")
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:\xc3\xa5\xc3\xa4\xc3\xb6\r\n"
|
||||
b"DTSTART;VALUE=DATE-TIME:20120903T000000\r\nEND:VEVENT\r\n"
|
||||
)
|
||||
|
||||
def test_issue_70(self):
|
||||
"""Issue #70 - e.decode("RRULE") causes Attribute Error
|
||||
https://github.com/collective/icalendar/issues/70
|
||||
"""
|
||||
|
||||
ical_str = """BEGIN:VEVENT
|
||||
CREATED:20081114T072804Z
|
||||
UID:D449CA84-00A3-4E55-83E1-34B58268853B
|
||||
DTEND:20070220T180000
|
||||
RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20070619T225959
|
||||
TRANSP:OPAQUE
|
||||
SUMMARY:Esb mellon phone conf
|
||||
DTSTART:20070220T170000
|
||||
DTSTAMP:20070221T095412Z
|
||||
SEQUENCE:0
|
||||
END:VEVENT"""
|
||||
|
||||
cal = icalendar.Calendar.from_ical(ical_str)
|
||||
recur = cal.decoded("RRULE")
|
||||
self.assertIsInstance(recur, icalendar.vRecur)
|
||||
self.assertEqual(
|
||||
recur.to_ical(),
|
||||
b'FREQ=WEEKLY;UNTIL=20070619T225959;INTERVAL=1'
|
||||
)
|
||||
|
||||
def test_issue_82(self):
|
||||
"""Issue #82 - vBinary __repr__ called rather than to_ical from
|
||||
container types
|
||||
https://github.com/collective/icalendar/issues/82
|
||||
"""
|
||||
|
||||
b = icalendar.vBinary('text')
|
||||
b.params['FMTTYPE'] = 'text/plain'
|
||||
self.assertEqual(b.to_ical(), b'dGV4dA==')
|
||||
e = icalendar.Event()
|
||||
e.add('ATTACH', b)
|
||||
self.assertEqual(
|
||||
e.to_ical(),
|
||||
b"BEGIN:VEVENT\r\nATTACH;ENCODING=BASE64;FMTTYPE=text/plain;"
|
||||
b"VALUE=BINARY:dGV4dA==\r\nEND:VEVENT\r\n"
|
||||
)
|
||||
|
||||
def test_issue_100(self):
|
||||
"""Issue #100 - Transformed doctests into unittests, Test fixes and
|
||||
cleanup.
|
||||
https://github.com/collective/icalendar/pull/100
|
||||
"""
|
||||
|
||||
ical_content = "BEGIN:VEVENT\r\nSUMMARY;LANGUAGE=ru:te\r\nEND:VEVENT"
|
||||
icalendar.Event.from_ical(ical_content).to_ical()
|
||||
|
||||
def test_issue_101(self):
|
||||
"""Issue #101 - icalender is choking on umlauts in ORGANIZER
|
||||
|
||||
https://github.com/collective/icalendar/issues/101
|
||||
"""
|
||||
ical_str = """BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
X-WR-CALNAME:Kalender von acme\, admin
|
||||
PRODID:-//The Horde Project//Horde_iCalendar Library\, Horde 3.3.5//EN
|
||||
METHOD:PUBLISH
|
||||
BEGIN:VEVENT
|
||||
DTSTART:20130416T100000Z
|
||||
DTEND:20130416T110000Z
|
||||
DTSTAMP:20130416T092616Z
|
||||
UID:20130416112341.10064jz0k4j7uem8@acmenet.de
|
||||
CREATED:20130416T092341Z
|
||||
LAST-MODIFIED:20130416T092341Z
|
||||
SUMMARY:wichtiger termin 1
|
||||
ORGANIZER;CN="acme, ädmin":mailto:adm-acme@mydomain.de
|
||||
LOCATION:im büro
|
||||
CLASS:PUBLIC
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:OPAQUE
|
||||
END:VEVENT
|
||||
END:VCALENDAR"""
|
||||
|
||||
cal = icalendar.Calendar.from_ical(ical_str)
|
||||
org_cn = cal.walk('VEVENT')[0]['ORGANIZER'].params['CN']
|
||||
self.assertEqual(org_cn, u'acme, ädmin')
|
||||
|
||||
def test_issue_112(self):
|
||||
"""Issue #112 - No timezone info on EXDATE
|
||||
https://github.com/collective/icalendar/issues/112
|
||||
"""
|
||||
directory = os.path.dirname(__file__)
|
||||
path = os.path.join(directory,
|
||||
'issue_112_missing_tzinfo_on_exdate.ics')
|
||||
with open(path, 'rb') as ics:
|
||||
cal = icalendar.Calendar.from_ical(ics.read())
|
||||
event = cal.walk('VEVENT')[0]
|
||||
|
||||
event_ical = to_unicode(event.to_ical()) # Py3 str type doesn't
|
||||
# support buffer API
|
||||
# General timezone aware dates in ical string
|
||||
self.assertTrue('DTSTART;TZID=America/New_York:20130907T120000'
|
||||
in event_ical)
|
||||
self.assertTrue('DTEND;TZID=America/New_York:20130907T170000'
|
||||
in event_ical)
|
||||
# Specific timezone aware exdates in ical string
|
||||
self.assertTrue('EXDATE;TZID=America/New_York:20131012T120000'
|
||||
in event_ical)
|
||||
self.assertTrue('EXDATE;TZID=America/New_York:20131011T120000'
|
||||
in event_ical)
|
||||
|
||||
self.assertEqual(event['exdate'][0].dts[0].dt.tzname(), 'EDT')
|
||||
|
||||
def test_issue_114(self):
|
||||
"""Issue #114/#115 - invalid line in event breaks the parser
|
||||
https://github.com/collective/icalendar/issues/114
|
||||
"""
|
||||
|
||||
directory = os.path.dirname(__file__)
|
||||
ics = open(os.path.join(directory, 'issue_114_invalid_line.ics'), 'rb')
|
||||
with self.assertRaises(ValueError):
|
||||
cal = icalendar.Calendar.from_ical(ics.read())
|
||||
cal # pep 8
|
||||
ics.close()
|
||||
|
||||
def test_issue_116(self):
|
||||
"""Issue #116/#117 - How to add 'X-APPLE-STRUCTURED-LOCATION'
|
||||
"""
|
||||
event = icalendar.Event()
|
||||
event.add(
|
||||
"X-APPLE-STRUCTURED-LOCATION",
|
||||
"geo:-33.868900,151.207000",
|
||||
parameters={
|
||||
"VALUE": "URI",
|
||||
"X-ADDRESS": "367 George Street Sydney CBD NSW 2000",
|
||||
"X-APPLE-RADIUS": "72",
|
||||
"X-TITLE": "367 George Street"
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
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'
|
||||
)
|
||||
|
||||
# roundtrip
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
icalendar.Event.from_ical(event.to_ical()).to_ical()
|
||||
)
|
253
libs/icalendar-3.6.1/build/lib/icalendar/tests/test_icalendar.py
Normal file
253
libs/icalendar-3.6.1/build/lib/icalendar/tests/test_icalendar.py
Normal file
@ -0,0 +1,253 @@
|
||||
# coding: utf-8
|
||||
from icalendar.tests import unittest
|
||||
|
||||
|
||||
class IcalendarTestCase (unittest.TestCase):
|
||||
|
||||
def test_long_lines(self):
|
||||
from ..parser import Contentlines, Contentline
|
||||
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'
|
||||
)
|
||||
|
||||
# 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', '']
|
||||
)
|
||||
|
||||
def test_contentline_class(self):
|
||||
from ..parser import Contentline, Parameters
|
||||
from ..prop import vText
|
||||
|
||||
self.assertEqual(
|
||||
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()
|
||||
self.assertEqual(
|
||||
c,
|
||||
(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 ')
|
||||
)
|
||||
|
||||
# http://tools.ietf.org/html/rfc5545#section-3.3.11
|
||||
# An intentional formatted text line break MUST only be included in
|
||||
# a "TEXT" property value by representing the line break with the
|
||||
# character sequence of BACKSLASH, followed by a LATIN SMALL LETTER
|
||||
# N or a LATIN CAPITAL LETTER N, that is "\n" or "\N".
|
||||
|
||||
# Newlines are not allwoed in content lines
|
||||
self.assertRaises(AssertionError, Contentline, b'1234\r\n\r\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.')
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
# It can parse itself into parts,
|
||||
# which is a tuple of (name, params, vals)
|
||||
self.assertEqual(
|
||||
Contentline('dtstart:20050101T120000').parts(),
|
||||
('dtstart', Parameters({}), '20050101T120000')
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('dtstart;value=datetime:20050101T120000').parts(),
|
||||
('dtstart', Parameters({'VALUE': 'datetime'}), '20050101T120000')
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
self.assertEqual(
|
||||
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')
|
||||
self.assertEqual(
|
||||
Contentline.from_parts(*parts),
|
||||
'ATTENDEE;CN="Max Rasmussen";ROLE=REQ-PARTICIPANT:'
|
||||
'MAILTO:maxm@example.com'
|
||||
)
|
||||
|
||||
# and again
|
||||
parts = ('ATTENDEE', Parameters(), 'MAILTO:maxm@example.com')
|
||||
self.assertEqual(
|
||||
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'))
|
||||
self.assertEqual(
|
||||
Contentline.from_parts(*parts),
|
||||
'ATTENDEE:MAILTO:test@example.com'
|
||||
)
|
||||
|
||||
# A value in UTF-8
|
||||
parts = ('SUMMARY', Parameters(), vText('INternational char æ ø å'))
|
||||
self.assertEqual(
|
||||
Contentline.from_parts(*parts),
|
||||
u'SUMMARY:INternational char æ ø å'
|
||||
)
|
||||
|
||||
# A value can also be unicode
|
||||
parts = ('SUMMARY', Parameters(), vText(u'INternational char æ ø å'))
|
||||
self.assertEqual(
|
||||
Contentline.from_parts(*parts),
|
||||
u'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(
|
||||
sorted(params.items()),
|
||||
sorted([('ROLE', 'REQ-PARTICIPANT'), ('CN', 'Max Rasmussen')])
|
||||
)
|
||||
|
||||
# And the traditional failure
|
||||
with self.assertRaisesRegexp(
|
||||
ValueError,
|
||||
'Content line could not be parsed into parts'
|
||||
):
|
||||
Contentline('ATTENDEE;maxm@example.com').parts()
|
||||
|
||||
# Another failure:
|
||||
with self.assertRaisesRegexp(
|
||||
ValueError,
|
||||
'Content line could not be parsed into parts'
|
||||
):
|
||||
Contentline(':maxm@example.com').parts()
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('key;param=:value').parts(),
|
||||
('key', Parameters({'PARAM': ''}), 'value')
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('key;param="pvalue":value').parts(),
|
||||
('key', Parameters({'PARAM': 'pvalue'}), 'value')
|
||||
)
|
||||
|
||||
# Should bomb on missing param:
|
||||
with self.assertRaisesRegexp(
|
||||
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')
|
||||
)
|
||||
|
||||
# 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')
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('key;param="pValue":value', strict=True).parts(),
|
||||
('key', Parameters({'PARAM': 'pValue'}), 'value')
|
||||
)
|
||||
|
||||
def test_fold_line(self):
|
||||
from ..parser import foldline
|
||||
|
||||
self.assertEqual(foldline(u'foo'), u'foo')
|
||||
self.assertEqual(
|
||||
foldline(u"Lorem ipsum dolor sit amet, consectetur adipiscing "
|
||||
u"elit. Vestibulum convallis imperdiet dui posuere."),
|
||||
(u'Lorem ipsum dolor sit amet, consectetur adipiscing elit. '
|
||||
u'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(u'привет'.encode('utf-8'), limit=3)
|
||||
|
||||
self.assertEqual(foldline(u'foobar', limit=4), u'foo\r\n bar')
|
||||
self.assertEqual(
|
||||
foldline(u'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
u'. Vestibulum convallis imperdiet dui posuere.'),
|
||||
(u'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
|
||||
u' Vestibulum conval\r\n lis imperdiet dui posuere.')
|
||||
)
|
||||
self.assertEqual(
|
||||
foldline(u'DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ'),
|
||||
u'DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭ\r\n ЮЯ'
|
||||
)
|
||||
|
||||
def test_value_double_quoting(self):
|
||||
from ..parser import dquote
|
||||
self.assertEqual(dquote('Max'), 'Max')
|
||||
self.assertEqual(dquote('Rasmussen, Max'), '"Rasmussen, Max"')
|
||||
self.assertEqual(dquote('name:value'), '"name:value"')
|
||||
|
||||
def test_q_split(self):
|
||||
from ..parser import q_split
|
||||
self.assertEqual(q_split('Max,Moller,"Rasmussen, Max"'),
|
||||
['Max', 'Moller', '"Rasmussen, Max"'])
|
||||
|
||||
def test_q_join(self):
|
||||
from ..parser import q_join
|
||||
self.assertEqual(q_join(['Max', 'Moller', 'Rasmussen, Max']),
|
||||
'Max,Moller,"Rasmussen, Max"')
|
@ -0,0 +1,28 @@
|
||||
from icalendar import Calendar
|
||||
from icalendar.prop import vText
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import os
|
||||
|
||||
|
||||
class TestMultiple(unittest.TestCase):
|
||||
"""A example with multiple VCALENDAR components"""
|
||||
|
||||
def test_multiple(self):
|
||||
|
||||
directory = os.path.dirname(__file__)
|
||||
cals = Calendar.from_ical(
|
||||
open(os.path.join(directory, 'multiple.ics'), 'rb').read(),
|
||||
multiple=True
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
@ -0,0 +1,207 @@
|
||||
# coding: utf-8
|
||||
from icalendar import Calendar
|
||||
from icalendar import Event
|
||||
from icalendar import Parameters
|
||||
from icalendar import vCalAddress
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import icalendar
|
||||
|
||||
|
||||
class TestPropertyParams(unittest.TestCase):
|
||||
|
||||
def test_property_params(self):
|
||||
# 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.params["CN"] = "Doe, John"
|
||||
ical = Calendar()
|
||||
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"""
|
||||
|
||||
self.assertEqual(ical_str, exp_str)
|
||||
|
||||
# other way around: ensure the property parameters can be restored from
|
||||
# an icalendar string.
|
||||
ical2 = Calendar.from_ical(ical_str)
|
||||
self.assertEqual(ical2.get('ORGANIZER').params.get('CN'), 'Doe, John')
|
||||
|
||||
def test_unicode_param(self):
|
||||
cal_address = vCalAddress('mailto:john.doe@example.org')
|
||||
cal_address.params["CN"] = "Джон Доу"
|
||||
vevent = Event()
|
||||
vevent['ORGANIZER'] = cal_address
|
||||
self.assertEqual(
|
||||
vevent.to_ical().decode('utf-8'),
|
||||
u'BEGIN:VEVENT\r\n'
|
||||
u'ORGANIZER;CN="Джон Доу":mailto:john.doe@example.org\r\n'
|
||||
u'END:VEVENT\r\n'
|
||||
)
|
||||
|
||||
self.assertEqual(vevent['ORGANIZER'].params['CN'],
|
||||
'Джон Доу')
|
||||
|
||||
def test_quoting(self):
|
||||
# not double-quoted
|
||||
self._test_quoting(u"Aramis", u'Aramis')
|
||||
# if a space is present - enclose in double quotes
|
||||
self._test_quoting(u"Aramis Alameda", u'"Aramis Alameda"')
|
||||
# a single quote in parameter value - double quote the value
|
||||
self._test_quoting(u"Aramis d'Alameda", u'"Aramis d\'Alameda"')
|
||||
# double quote is replaced with single quote
|
||||
self._test_quoting(u"Aramis d\"Alameda", u'"Aramis d\'Alameda"')
|
||||
self._test_quoting(u"Арамис д'Аламеда", u'"Арамис д\'Аламеда"')
|
||||
|
||||
def _test_quoting(self, cn_param, cn_quoted):
|
||||
"""
|
||||
@param cn_param: CN parameter value to test for quoting
|
||||
@param cn_quoted: expected quoted parameter in icalendar format
|
||||
"""
|
||||
vevent = Event()
|
||||
attendee = vCalAddress('test@mail.com')
|
||||
attendee.params['CN'] = cn_param
|
||||
vevent.add('ATTENDEE', attendee)
|
||||
self.assertEqual(
|
||||
vevent.to_ical(),
|
||||
b'BEGIN:VEVENT\r\nATTENDEE;CN=' + cn_quoted.encode('utf-8') +
|
||||
b':test@mail.com\r\nEND:VEVENT\r\n'
|
||||
)
|
||||
|
||||
def test_escaping(self):
|
||||
# verify that escaped non safe chars are decoded correctly
|
||||
NON_SAFE_CHARS = u',\\;:'
|
||||
for char in NON_SAFE_CHARS:
|
||||
cn_escaped = u"Society\\%s 2014" % char
|
||||
cn_decoded = u"Society%s 2014" % char
|
||||
vevent = Event.from_ical(
|
||||
u'BEGIN:VEVENT\r\n'
|
||||
u'ORGANIZER;CN=%s:that\r\n'
|
||||
u'END:VEVENT\r\n' % cn_escaped
|
||||
)
|
||||
self.assertEqual(vevent['ORGANIZER'].params['CN'], cn_decoded)
|
||||
|
||||
vevent = Event.from_ical(
|
||||
'BEGIN:VEVENT\r\n'
|
||||
'ORGANIZER;CN=that\\, that\\; %th%%at%\\\\ that\\:'
|
||||
':это\\, то\\; that\\\\ %th%%at%\\:\r\n'
|
||||
'END:VEVENT\r\n'
|
||||
)
|
||||
self.assertEqual(
|
||||
vevent['ORGANIZER'].params['CN'],
|
||||
r'that, that; %th%%at%\ that:'
|
||||
)
|
||||
self.assertEqual(
|
||||
vevent['ORGANIZER'].to_ical().decode('utf-8'),
|
||||
u'это, то; that\\ %th%%at%:'
|
||||
)
|
||||
|
||||
def test_parameters_class(self):
|
||||
|
||||
# Simple parameter:value pair
|
||||
p = Parameters(parameter1='Value1')
|
||||
self.assertEqual(p.to_ical(), b'PARAMETER1=Value1')
|
||||
|
||||
# keys are converted to upper
|
||||
self.assertEqual(list(p.keys()), ['PARAMETER1'])
|
||||
|
||||
# Parameters are case insensitive
|
||||
self.assertEqual(p['parameter1'], 'Value1')
|
||||
self.assertEqual(p['PARAMETER1'], 'Value1')
|
||||
|
||||
# Parameter with list of values must be seperated by comma
|
||||
p = Parameters({'parameter1': ['Value1', 'Value2']})
|
||||
self.assertEqual(p.to_ical(), b'PARAMETER1=Value1,Value2')
|
||||
|
||||
# Multiple parameters must be seperated by a semicolon
|
||||
p = Parameters({'RSVP': 'TRUE', 'ROLE': 'REQ-PARTICIPANT'})
|
||||
self.assertEqual(p.to_ical(), b'ROLE=REQ-PARTICIPANT;RSVP=TRUE')
|
||||
|
||||
# Parameter values containing ',;:' must be double quoted
|
||||
p = Parameters({'ALTREP': 'http://www.wiz.org'})
|
||||
self.assertEqual(p.to_ical(), b'ALTREP="http://www.wiz.org"')
|
||||
|
||||
# list items must be quoted seperately
|
||||
p = Parameters({'MEMBER': ['MAILTO:projectA@host.com',
|
||||
'MAILTO:projectB@host.com']})
|
||||
self.assertEqual(
|
||||
p.to_ical(),
|
||||
b'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"'
|
||||
)
|
||||
|
||||
# Now the whole sheebang
|
||||
p = Parameters({'parameter1': 'Value1',
|
||||
'parameter2': ['Value2', 'Value3'],
|
||||
'ALTREP': ['http://www.wiz.org', 'value4']})
|
||||
self.assertEqual(
|
||||
p.to_ical(),
|
||||
(b'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;'
|
||||
b'PARAMETER2=Value2,Value3')
|
||||
)
|
||||
|
||||
# We can also parse parameter strings
|
||||
self.assertEqual(
|
||||
Parameters.from_ical('PARAMETER1=Value 1;param2=Value 2'),
|
||||
Parameters({'PARAMETER1': 'Value 1', 'PARAM2': 'Value 2'})
|
||||
)
|
||||
|
||||
# Including empty strings
|
||||
self.assertEqual(Parameters.from_ical('param='),
|
||||
Parameters({'PARAM': ''}))
|
||||
|
||||
# We can also parse parameter strings
|
||||
self.assertEqual(
|
||||
Parameters.from_ical(
|
||||
'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"'
|
||||
),
|
||||
Parameters({'MEMBER': ['MAILTO:projectA@host.com',
|
||||
'MAILTO:projectB@host.com']})
|
||||
)
|
||||
|
||||
# We can also parse parameter strings
|
||||
self.assertEqual(
|
||||
Parameters.from_ical('ALTREP="http://www.wiz.org",value4;'
|
||||
'PARAMETER1=Value1;PARAMETER2=Value2,Value3'),
|
||||
Parameters({'PARAMETER1': 'Value1',
|
||||
'ALTREP': ['http://www.wiz.org', 'value4'],
|
||||
'PARAMETER2': ['Value2', 'Value3']})
|
||||
)
|
||||
|
||||
def test_parse_and_access_property_params(self):
|
||||
"""Parse an ics string and access some property parameters then.
|
||||
This is a follow-up of a question recieved per email.
|
||||
|
||||
"""
|
||||
ics = """BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID://RESEARCH IN MOTION//BIS 3.0
|
||||
METHOD:REQUEST
|
||||
BEGIN:VEVENT
|
||||
SEQUENCE:2
|
||||
X-RIM-REVISION:0
|
||||
SUMMARY:Test meeting from BB
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
CLASS:PUBLIC
|
||||
ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN="RembrandXS":MAILTO:rembrand@xs4all.nl
|
||||
ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN="RembrandDX":MAILTO:rembrand@daxlab.com
|
||||
ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN="RembrandSB":MAILTO:rembspam@xs4all.nl
|
||||
UID:XRIMCAL-628059586-522954492-9750559
|
||||
DTSTART;VALUE=DATE:20120814
|
||||
DTEND;VALUE=DATE:20120815
|
||||
DESCRIPTION:Test meeting from BB
|
||||
DTSTAMP:20120813T151458Z
|
||||
ORGANIZER:mailto:rembrand@daxlab.com
|
||||
END:VEVENT
|
||||
END:VCALENDAR"""
|
||||
|
||||
cal = icalendar.Calendar.from_ical(ics)
|
||||
event = cal.walk("VEVENT")[0]
|
||||
event['attendee'][0]
|
||||
self.assertEqual(event['attendee'][0].to_ical(),
|
||||
b'MAILTO:rembrand@xs4all.nl')
|
||||
self.assertEqual(event['attendee'][0].params.to_ical(),
|
||||
b'CN=RembrandXS;PARTSTAT=NEEDS-ACTION;RSVP=TRUE')
|
||||
self.assertEqual(event['attendee'][0].params['cn'], u'RembrandXS')
|
@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from icalendar.caselessdict import CaselessDict
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import datetime
|
||||
import icalendar
|
||||
import os
|
||||
import pytz
|
||||
|
||||
|
||||
class TestRecurrence(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
directory = os.path.dirname(__file__)
|
||||
self.cal = icalendar.Calendar.from_ical(
|
||||
open(os.path.join(directory, 'recurrence.ics'), 'rb').read()
|
||||
)
|
||||
|
||||
def test_recurrence_exdates_one_line(self):
|
||||
first_event = self.cal.walk('vevent')[0]
|
||||
|
||||
self.assertIsInstance(first_event, CaselessDict)
|
||||
self.assertEqual(
|
||||
first_event['rrule'], {'COUNT': [100], 'FREQ': ['DAILY']}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
first_event['exdate'].to_ical(),
|
||||
b'19960402T010000Z,19960403T010000Z,19960404T010000Z'
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
first_event['exdate'].dts[0].dt,
|
||||
datetime.datetime(1996, 4, 2, 1, 0, tzinfo=pytz.utc)
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
first_event['exdate'].dts[1].dt,
|
||||
datetime.datetime(1996, 4, 3, 1, 0, tzinfo=pytz.utc)
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
first_event['exdate'].dts[2].dt,
|
||||
datetime.datetime(1996, 4, 4, 1, 0, tzinfo=pytz.utc)
|
||||
)
|
||||
|
||||
def test_recurrence_exdates_multiple_lines(self):
|
||||
event = self.cal.walk('vevent')[1]
|
||||
|
||||
exdate = event['exdate']
|
||||
|
||||
# TODO: DOCUMENT BETTER!
|
||||
# In this case we have multiple EXDATE definitions, one per line.
|
||||
# Icalendar makes a list out of this instead of zipping it into one
|
||||
# vDDDLists object. Actually, this feels correct for me, as it also
|
||||
# allows to define different timezones per exdate line - but client
|
||||
# code has to handle this as list and not blindly expecting to be able
|
||||
# to call event['EXDATE'].to_ical() on it:
|
||||
self.assertEqual(isinstance(exdate, list), True) # multiple EXDATE
|
||||
self.assertEqual(exdate[0].to_ical(), b'20120529T100000')
|
||||
|
||||
# TODO: test for embedded timezone information!
|
29
libs/icalendar-3.6.1/build/lib/icalendar/tests/test_time.py
Normal file
29
libs/icalendar-3.6.1/build/lib/icalendar/tests/test_time.py
Normal file
@ -0,0 +1,29 @@
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import datetime
|
||||
import icalendar
|
||||
import os
|
||||
|
||||
|
||||
class TestTime(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
icalendar.cal.types_factory.types_map['X-SOMETIME'] = 'time'
|
||||
|
||||
def tearDown(self):
|
||||
icalendar.cal.types_factory.types_map.pop('X-SOMETIME')
|
||||
|
||||
def test_create_from_ical(self):
|
||||
directory = os.path.dirname(__file__)
|
||||
ics = open(os.path.join(directory, 'time.ics'), 'rb')
|
||||
cal = icalendar.Calendar.from_ical(ics.read())
|
||||
ics.close()
|
||||
|
||||
self.assertEqual(cal['X-SOMETIME'].dt, datetime.time(17, 20, 10))
|
||||
self.assertEqual(cal['X-SOMETIME'].to_ical(), '172010')
|
||||
|
||||
def test_create_to_ical(self):
|
||||
cal = icalendar.Calendar()
|
||||
cal.add('X-SOMETIME', datetime.time(17, 20, 10))
|
||||
self.assertTrue(b'X-SOMETIME;VALUE=TIME:172010' in
|
||||
cal.to_ical().splitlines())
|
141
libs/icalendar-3.6.1/build/lib/icalendar/tests/test_timezoned.py
Normal file
141
libs/icalendar-3.6.1/build/lib/icalendar/tests/test_timezoned.py
Normal file
@ -0,0 +1,141 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import datetime
|
||||
import dateutil.parser
|
||||
import icalendar
|
||||
import os
|
||||
import pytz
|
||||
|
||||
|
||||
class TestTimezoned(unittest.TestCase):
|
||||
|
||||
def test_create_from_ical(self):
|
||||
directory = os.path.dirname(__file__)
|
||||
cal = icalendar.Calendar.from_ical(
|
||||
open(os.path.join(directory, 'timezoned.ics'), 'rb').read()
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
cal['prodid'].to_ical(),
|
||||
b"-//Plone.org//NONSGML plone.app.event//EN"
|
||||
)
|
||||
|
||||
timezones = cal.walk('VTIMEZONE')
|
||||
self.assertEqual(len(timezones), 1)
|
||||
|
||||
tz = timezones[0]
|
||||
self.assertEqual(tz['tzid'].to_ical(), b"Europe/Vienna")
|
||||
|
||||
std = tz.walk('STANDARD')[0]
|
||||
self.assertEqual(
|
||||
std.decoded('TZOFFSETFROM'),
|
||||
datetime.timedelta(0, 7200)
|
||||
)
|
||||
|
||||
ev1 = cal.walk('VEVENT')[0]
|
||||
self.assertEqual(
|
||||
ev1.decoded('DTSTART'),
|
||||
datetime.datetime(2012, 2, 13, 10, 0, 0,
|
||||
tzinfo=pytz.timezone('Europe/Vienna')))
|
||||
self.assertEqual(
|
||||
ev1.decoded('DTSTAMP'),
|
||||
datetime.datetime(2010, 10, 10, 9, 10, 10, tzinfo=pytz.utc))
|
||||
|
||||
def test_create_to_ical(self):
|
||||
cal = icalendar.Calendar()
|
||||
|
||||
cal.add('prodid', u"-//Plone.org//NONSGML plone.app.event//EN")
|
||||
cal.add('version', u"2.0")
|
||||
cal.add('x-wr-calname', u"test create calendar")
|
||||
cal.add('x-wr-caldesc', u"icalendar tests")
|
||||
cal.add('x-wr-relcalid', u"12345")
|
||||
cal.add('x-wr-timezone', u"Europe/Vienna")
|
||||
|
||||
tzc = icalendar.Timezone()
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
tzc.add_component(tzs)
|
||||
tzc.add_component(tzd)
|
||||
cal.add_component(tzc)
|
||||
|
||||
event = icalendar.Event()
|
||||
tz = pytz.timezone("Europe/Vienna")
|
||||
event.add(
|
||||
'dtstart',
|
||||
datetime.datetime(2012, 2, 13, 10, 00, 00, tzinfo=tz))
|
||||
event.add(
|
||||
'dtend',
|
||||
datetime.datetime(2012, 2, 17, 18, 00, 00, tzinfo=tz))
|
||||
event.add(
|
||||
'dtstamp',
|
||||
datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=tz))
|
||||
event.add(
|
||||
'created',
|
||||
datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=tz))
|
||||
event.add('uid', u'123456')
|
||||
event.add(
|
||||
'last-modified',
|
||||
datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=tz))
|
||||
event.add('summary', u'artsprint 2012')
|
||||
# event.add('rrule', u'FREQ=YEARLY;INTERVAL=1;COUNT=10')
|
||||
event.add('description', u'sprinting at the artsprint')
|
||||
event.add('location', u'aka bild, wien')
|
||||
event.add('categories', u'first subject')
|
||||
event.add('categories', u'second subject')
|
||||
event.add('attendee', u'häns')
|
||||
event.add('attendee', u'franz')
|
||||
event.add('attendee', u'sepp')
|
||||
event.add('contact', u'Max Mustermann, 1010 Wien')
|
||||
event.add('url', u'http://plone.org')
|
||||
cal.add_component(event)
|
||||
|
||||
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;VALUE=DATE-TIME:19701025T03"
|
||||
"0000|RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10|RRULE:FREQ=YEARLY;B"
|
||||
"YDAY=-1SU;BYMONTH=3|TZNAME:CET|TZOFFSETFROM:+0200|TZOFFSETTO:+01"
|
||||
"00|END:STANDARD|BEGIN:DAYLIGHT|DTSTART;VALUE=DATE-TIME:19700329T"
|
||||
"020000|TZNAME:CEST|TZOFFSETFROM:+0100|TZOFFSETTO:+0200|END:DAYLI"
|
||||
"GHT|END:VTIMEZONE"
|
||||
self.assertTrue(vtimezone_lines in test_out)
|
||||
|
||||
test_str = "DTSTART;TZID=Europe/Vienna;VALUE=DATE-TIME:20120213T100000"
|
||||
self.assertTrue(test_str in test_out)
|
||||
self.assertTrue("ATTENDEE:sepp" in test_out)
|
||||
|
||||
# ical standard expects DTSTAMP and CREATED in UTC
|
||||
self.assertTrue("DTSTAMP;VALUE=DATE-TIME:20101010T091010Z" in test_out)
|
||||
self.assertTrue("CREATED;VALUE=DATE-TIME:20101010T091010Z" in test_out)
|
||||
|
||||
def test_tzinfo_dateutil(self):
|
||||
# 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')
|
||||
self.assertTrue(date.tzinfo.__module__ == 'dateutil.tz')
|
||||
self.assertTrue(date2.tzinfo.__module__ == 'dateutil.tz')
|
||||
|
||||
# make sure, it's parsed properly and doesn't throw an error
|
||||
self.assertTrue(icalendar.vDDDTypes(date).to_ical()
|
||||
== b'20120830T224100Z')
|
||||
self.assertTrue(icalendar.vDDDTypes(date2).to_ical()
|
||||
== b'20120830T224100')
|
349
libs/icalendar-3.6.1/build/lib/icalendar/tests/test_unit_cal.py
Normal file
349
libs/icalendar-3.6.1/build/lib/icalendar/tests/test_unit_cal.py
Normal file
@ -0,0 +1,349 @@
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import icalendar
|
||||
import pytz
|
||||
|
||||
|
||||
class TestCalComponent(unittest.TestCase):
|
||||
|
||||
def test_cal_Component(self):
|
||||
from icalendar.cal import Component, Calendar, Event
|
||||
from icalendar import prop
|
||||
|
||||
# A component is like a dictionary with extra methods and attributes.
|
||||
c = Component()
|
||||
c.name = 'VCALENDAR'
|
||||
|
||||
# Every key defines a property.A property can consist of either a
|
||||
# single item. This can be set with a single value...
|
||||
c['prodid'] = '-//max m//icalendar.mxm.dk/'
|
||||
self.assertEqual(
|
||||
c,
|
||||
Calendar({'PRODID': '-//max m//icalendar.mxm.dk/'})
|
||||
)
|
||||
|
||||
# or with a list
|
||||
c['ATTENDEE'] = ['Max M', 'Rasmussen']
|
||||
self.assertEqual(
|
||||
c,
|
||||
Calendar({'ATTENDEE': ['Max M', 'Rasmussen'],
|
||||
'PRODID': '-//max m//icalendar.mxm.dk/'})
|
||||
)
|
||||
|
||||
### ADD MULTIPLE VALUES TO A PROPERTY
|
||||
|
||||
# if you use the add method you don't have to considder if a value is
|
||||
# a list or not.
|
||||
c = Component()
|
||||
c.name = 'VEVENT'
|
||||
|
||||
# add multiple values at once
|
||||
c.add('attendee',
|
||||
['test@test.com', 'test2@test.com'])
|
||||
|
||||
# or add one per line
|
||||
c.add('attendee', 'maxm@mxm.dk')
|
||||
c.add('attendee', 'test@example.dk')
|
||||
|
||||
# add again multiple values at once to very concatenaton of lists
|
||||
c.add('attendee',
|
||||
['test3@test.com', 'test4@test.com'])
|
||||
|
||||
self.assertEqual(
|
||||
c,
|
||||
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')
|
||||
]})
|
||||
)
|
||||
|
||||
###
|
||||
|
||||
# You can get the values back directly ...
|
||||
c.add('prodid', '-//my product//')
|
||||
self.assertEqual(c['prodid'], prop.vText(u'-//my product//'))
|
||||
|
||||
# ... or decoded to a python type
|
||||
self.assertEqual(c.decoded('prodid'), b'-//my product//')
|
||||
|
||||
# With default values for non existing properties
|
||||
self.assertEqual(c.decoded('version', 'No Version'), 'No Version')
|
||||
|
||||
c.add('rdate', [datetime(2013, 3, 28), datetime(2013, 3, 27)])
|
||||
self.assertTrue(isinstance(c.decoded('rdate'), prop.vDDDLists))
|
||||
|
||||
# The component can render itself in the RFC 2445 format.
|
||||
c = Component()
|
||||
c.name = 'VCALENDAR'
|
||||
c.add('attendee', 'Max M')
|
||||
self.assertEqual(
|
||||
c.to_ical(),
|
||||
b'BEGIN:VCALENDAR\r\nATTENDEE:Max M\r\nEND:VCALENDAR\r\n'
|
||||
)
|
||||
|
||||
# Components can be nested, so You can add a subcompont. Eg a calendar
|
||||
# holds events.
|
||||
e = Component(summary='A brief history of time')
|
||||
e.name = 'VEVENT'
|
||||
e.add('dtend', '20000102T000000', encode=0)
|
||||
e.add('dtstart', '20000101T000000', encode=0)
|
||||
self.assertEqual(
|
||||
e.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'
|
||||
)
|
||||
|
||||
c.add_component(e)
|
||||
self.assertEqual(
|
||||
c.subcomponents,
|
||||
[Event({'DTEND': '20000102T000000', 'DTSTART': '20000101T000000',
|
||||
'SUMMARY': 'A brief history of time'})]
|
||||
)
|
||||
|
||||
# We can walk over nested componentes with the walk method.
|
||||
self.assertEqual([i.name for i in c.walk()], ['VCALENDAR', 'VEVENT'])
|
||||
|
||||
# We can also just walk over specific component types, by filtering
|
||||
# them on their name.
|
||||
self.assertEqual([i.name for i in c.walk('VEVENT')], ['VEVENT'])
|
||||
|
||||
self.assertEqual(
|
||||
[i['dtstart'] for i in c.walk('VEVENT')],
|
||||
['20000101T000000']
|
||||
)
|
||||
|
||||
# We can enumerate property items recursively with the property_items
|
||||
# method.
|
||||
self.assertEqual(
|
||||
c.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')]
|
||||
)
|
||||
|
||||
# We can also enumerate property items just under the component.
|
||||
self.assertEqual(
|
||||
c.property_items(recursive=False),
|
||||
[('BEGIN', b'VCALENDAR'),
|
||||
('ATTENDEE', prop.vCalAddress('Max M')),
|
||||
('END', b'VCALENDAR')]
|
||||
)
|
||||
|
||||
sc = c.subcomponents[0]
|
||||
self.assertEqual(
|
||||
sc.property_items(recursive=False),
|
||||
[('BEGIN', b'VEVENT'), ('DTEND', '20000102T000000'),
|
||||
('DTSTART', '20000101T000000'),
|
||||
('SUMMARY', 'A brief history of time'), ('END', b'VEVENT')]
|
||||
)
|
||||
|
||||
# Text fields which span multiple mulitple lines require proper
|
||||
# indenting
|
||||
c = Calendar()
|
||||
c['description'] = u'Paragraph one\n\nParagraph two'
|
||||
self.assertEqual(
|
||||
c.to_ical(),
|
||||
b'BEGIN:VCALENDAR\r\nDESCRIPTION:Paragraph one\\n\\nParagraph two'
|
||||
+ b'\r\nEND:VCALENDAR\r\n'
|
||||
)
|
||||
|
||||
# INLINE properties have their values on one property line. Note the
|
||||
# double quoting of the value with a colon in it.
|
||||
c = Calendar()
|
||||
c['resources'] = 'Chair, Table, "Room: 42"'
|
||||
self.assertEqual(
|
||||
c,
|
||||
Calendar({'RESOURCES': 'Chair, Table, "Room: 42"'})
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
c.to_ical(),
|
||||
b'BEGIN:VCALENDAR\r\nRESOURCES:Chair\\, Table\\, "Room: 42"\r\n'
|
||||
+ b'END:VCALENDAR\r\n'
|
||||
)
|
||||
|
||||
# The inline values must be handled by the get_inline() and
|
||||
# set_inline() methods.
|
||||
self.assertEqual(
|
||||
c.get_inline('resources', decode=0),
|
||||
[u'Chair', u'Table', u'Room: 42']
|
||||
)
|
||||
|
||||
# These can also be decoded
|
||||
self.assertEqual(
|
||||
c.get_inline('resources', decode=1),
|
||||
[b'Chair', b'Table', b'Room: 42']
|
||||
)
|
||||
|
||||
# You can set them directly ...
|
||||
c.set_inline('resources', ['A', 'List', 'of', 'some, recources'],
|
||||
encode=1)
|
||||
self.assertEqual(c['resources'], 'A,List,of,"some, recources"')
|
||||
|
||||
# ... and back again
|
||||
self.assertEqual(
|
||||
c.get_inline('resources', decode=0),
|
||||
['A', 'List', 'of', 'some, recources']
|
||||
)
|
||||
|
||||
c['freebusy'] = '19970308T160000Z/PT3H,19970308T200000Z/PT1H,'\
|
||||
+ '19970308T230000Z/19970309T000000Z'
|
||||
self.assertEqual(
|
||||
c.get_inline('freebusy', decode=0),
|
||||
['19970308T160000Z/PT3H', '19970308T200000Z/PT1H',
|
||||
'19970308T230000Z/19970309T000000Z']
|
||||
)
|
||||
|
||||
freebusy = c.get_inline('freebusy', decode=1)
|
||||
self.assertTrue(isinstance(freebusy[0][0], datetime))
|
||||
self.assertTrue(isinstance(freebusy[0][1], timedelta))
|
||||
|
||||
def test_cal_Component_add(self):
|
||||
# Test the for timezone correctness: dtstart should preserve it's
|
||||
# timezone, crated, dtstamp and last-modified must be in UTC.
|
||||
Component = icalendar.cal.Component
|
||||
comp = Component()
|
||||
comp.add('dtstart', datetime(2010, 10, 10, 10, 0, 0,
|
||||
tzinfo=pytz.timezone("Europe/Vienna")))
|
||||
comp.add('created', datetime(2010, 10, 10, 12, 0, 0))
|
||||
comp.add('dtstamp', datetime(2010, 10, 10, 14, 0, 0,
|
||||
tzinfo=pytz.timezone("Europe/Vienna")))
|
||||
comp.add('last-modified', datetime(2010, 10, 10, 16, 0, 0,
|
||||
tzinfo=pytz.utc))
|
||||
|
||||
lines = comp.to_ical().splitlines()
|
||||
self.assertTrue(
|
||||
b"DTSTART;TZID=Europe/Vienna;VALUE=DATE-TIME:20101010T100000"
|
||||
in lines)
|
||||
self.assertTrue(b"CREATED;VALUE=DATE-TIME:20101010T120000Z" in lines)
|
||||
self.assertTrue(b"DTSTAMP;VALUE=DATE-TIME:20101010T130000Z" in lines)
|
||||
self.assertTrue(
|
||||
b"LAST-MODIFIED;VALUE=DATE-TIME:20101010T160000Z" in lines
|
||||
)
|
||||
|
||||
def test_cal_Component_add_no_reencode(self):
|
||||
"""Already encoded values should not be re-encoded.
|
||||
"""
|
||||
from icalendar import cal, prop
|
||||
comp = cal.Component()
|
||||
comp.add('ATTACH', 'me')
|
||||
|
||||
comp.add('ATTACH', 'you', encode=False)
|
||||
binary = prop.vBinary('us')
|
||||
comp.add('ATTACH', binary)
|
||||
|
||||
self.assertEqual(comp['ATTACH'], [u'me', 'you', binary])
|
||||
|
||||
def test_cal_Component_add_property_parameter(self):
|
||||
# Test the for timezone correctness: dtstart should preserve it's
|
||||
# timezone, crated, dtstamp and last-modified must be in UTC.
|
||||
Component = icalendar.cal.Component
|
||||
comp = Component()
|
||||
comp.add('X-TEST-PROP', 'tryout.',
|
||||
parameters={'prop1': 'val1', 'prop2': 'val2'})
|
||||
lines = comp.to_ical().splitlines()
|
||||
self.assertTrue(b"X-TEST-PROP;PROP1=val1;PROP2=val2:tryout." in lines)
|
||||
|
||||
def test_cal_Component_from_ical(self):
|
||||
# Check for proper handling of TZID parameter of datetime properties
|
||||
Component = icalendar.cal.Component
|
||||
for component_name, property_name in (
|
||||
('VEVENT', 'DTSTART'),
|
||||
('VEVENT', 'DTEND'),
|
||||
('VEVENT', 'RECURRENCE-ID'),
|
||||
('VTODO', 'DUE')
|
||||
):
|
||||
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)
|
||||
self.assertEqual(str(component[property_name].dt.tzinfo.zone),
|
||||
"America/Denver")
|
||||
|
||||
component_str = 'BEGIN:' + component_name + '\n'
|
||||
component_str += property_name + ':'
|
||||
component_str += '20120404T073000\nEND:' + component_name
|
||||
component = Component.from_ical(component_str)
|
||||
self.assertEqual(component[property_name].dt.tzinfo,
|
||||
None)
|
||||
|
||||
|
||||
class TestCal(unittest.TestCase):
|
||||
|
||||
def test_cal_ComponentFactory(self):
|
||||
ComponentFactory = icalendar.cal.ComponentFactory
|
||||
factory = ComponentFactory()
|
||||
component = factory['VEVENT']
|
||||
event = component(dtstart='19700101')
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
b'BEGIN:VEVENT\r\nDTSTART:19700101\r\nEND:VEVENT\r\n'
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
factory.get('VCALENDAR', icalendar.cal.Component),
|
||||
icalendar.cal.Calendar)
|
||||
|
||||
def test_cal_Calendar(self):
|
||||
# Setting up a minimal calendar component looks like this
|
||||
cal = icalendar.cal.Calendar()
|
||||
|
||||
# Some properties are required to be compliant
|
||||
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 = icalendar.cal.Event()
|
||||
event['summary'] = 'Python meeting about calendaring'
|
||||
event['uid'] = '42'
|
||||
event.add('dtstart', datetime(2005, 4, 4, 8, 0, 0))
|
||||
cal.add_component(event)
|
||||
self.assertEqual(
|
||||
cal.subcomponents[0].to_ical(),
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Python meeting about calendaring\r\n'
|
||||
+ b'DTSTART;VALUE=DATE-TIME:20050404T080000\r\nUID:42\r\n'
|
||||
+ b'END:VEVENT\r\n')
|
||||
|
||||
# Write to disc
|
||||
import tempfile
|
||||
import os
|
||||
directory = tempfile.mkdtemp()
|
||||
open(os.path.join(directory, 'test.ics'), 'wb').write(cal.to_ical())
|
||||
|
||||
# Parsing a complete calendar from a string will silently ignore bogus
|
||||
# events. The bogosity in the following is the third EXDATE: it has an
|
||||
# empty DATE.
|
||||
s = '\r\n'.join(('BEGIN:VCALENDAR',
|
||||
'PRODID:-//Google Inc//Google Calendar 70.9054//EN',
|
||||
'VERSION:2.0',
|
||||
'CALSCALE:GREGORIAN',
|
||||
'METHOD:PUBLISH',
|
||||
'BEGIN:VEVENT',
|
||||
'DESCRIPTION:Perfectly OK event',
|
||||
'DTSTART;VALUE=DATE:20080303',
|
||||
'DTEND;VALUE=DATE:20080304',
|
||||
'RRULE:FREQ=DAILY;UNTIL=20080323T235959Z',
|
||||
'EXDATE;VALUE=DATE:20080311',
|
||||
'END:VEVENT',
|
||||
'BEGIN:VEVENT',
|
||||
'DESCRIPTION:Bogus event',
|
||||
'DTSTART;VALUE=DATE:20080303',
|
||||
'DTEND;VALUE=DATE:20080304',
|
||||
'RRULE:FREQ=DAILY;UNTIL=20080323T235959Z',
|
||||
'EXDATE;VALUE=DATE:20080311',
|
||||
'EXDATE;VALUE=DATE:',
|
||||
'END:VEVENT',
|
||||
'END:VCALENDAR'))
|
||||
self.assertEqual(
|
||||
[e['DESCRIPTION'].to_ical()
|
||||
for e in icalendar.cal.Calendar.from_ical(s).walk('VEVENT')],
|
||||
[b'Perfectly OK event'])
|
@ -0,0 +1,90 @@
|
||||
from icalendar.tests import unittest
|
||||
|
||||
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']
|
||||
|
||||
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 = 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 = dict(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)]
|
||||
)
|
||||
|
||||
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)]
|
||||
)
|
||||
|
||||
def test_CaselessDict(self):
|
||||
CaselessDict = icalendar.caselessdict.CaselessDict
|
||||
|
||||
ncd = CaselessDict(key1='val1', key2='val2')
|
||||
self.assertEqual(
|
||||
ncd,
|
||||
CaselessDict({'KEY2': 'val2', 'KEY1': 'val1'})
|
||||
)
|
||||
|
||||
self.assertEqual(ncd['key1'], 'val1')
|
||||
self.assertEqual(ncd['KEY1'], 'val1')
|
||||
|
||||
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)
|
||||
|
||||
del ncd['key4']
|
||||
self.assertFalse('key4' in ncd)
|
||||
|
||||
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'])
|
@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from icalendar.parser_tools import data_encode
|
||||
from icalendar.parser_tools import to_unicode
|
||||
from icalendar.tests import unittest
|
||||
|
||||
|
||||
class TestParserTools(unittest.TestCase):
|
||||
|
||||
def test_parser_tools_to_unicode(self):
|
||||
|
||||
self.assertEqual(to_unicode('spam'), u'spam')
|
||||
self.assertEqual(to_unicode(u'spam'), u'spam')
|
||||
self.assertEqual(to_unicode(u'spam'.encode('utf-8')), u'spam')
|
||||
self.assertEqual(to_unicode(b'\xc6\xb5'), u'\u01b5')
|
||||
self.assertEqual(to_unicode(u'\xc6\xb5'.encode('iso-8859-1')),
|
||||
u'\u01b5')
|
||||
self.assertEqual(to_unicode(b'\xc6\xb5', encoding='ascii'), u'\u01b5')
|
||||
self.assertEqual(to_unicode(1), 1)
|
||||
self.assertEqual(to_unicode(None), None)
|
||||
|
||||
def test_parser_tools_data_encode(self):
|
||||
|
||||
data1 = {
|
||||
u'k1': u'v1', 'k2': 'v2', u'k3': u'v3',
|
||||
'li1': ['it1', u'it2', {'k4': u'v4', u'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]}
|
||||
self.assertEqual(data_encode(data1), res)
|
497
libs/icalendar-3.6.1/build/lib/icalendar/tests/test_unit_prop.py
Normal file
497
libs/icalendar-3.6.1/build/lib/icalendar/tests/test_unit_prop.py
Normal file
@ -0,0 +1,497 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
from datetime import time
|
||||
from datetime import timedelta
|
||||
from icalendar.parser import Parameters
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import pytz
|
||||
|
||||
|
||||
class TestProp(unittest.TestCase):
|
||||
|
||||
def test_prop_vBinary(self):
|
||||
from ..prop import vBinary
|
||||
|
||||
txt = b'This is gibberish'
|
||||
txt_ical = b'VGhpcyBpcyBnaWJiZXJpc2g='
|
||||
self.assertEqual(vBinary(txt).to_ical(), txt_ical)
|
||||
self.assertEqual(vBinary.from_ical(txt_ical), txt)
|
||||
|
||||
# The roundtrip test
|
||||
txt = b'Binary data \x13 \x56'
|
||||
txt_ical = b'QmluYXJ5IGRhdGEgEyBW'
|
||||
self.assertEqual(vBinary(txt).to_ical(), txt_ical)
|
||||
self.assertEqual(vBinary.from_ical(txt_ical), txt)
|
||||
|
||||
self.assertIsInstance(vBinary('txt').params, Parameters)
|
||||
self.assertEqual(
|
||||
vBinary('txt').params, {'VALUE': 'BINARY', 'ENCODING': 'BASE64'}
|
||||
)
|
||||
|
||||
# Long data should not have line breaks, as that would interfere
|
||||
txt = b'a' * 99
|
||||
txt_ical = b'YWFh' * 33
|
||||
self.assertEqual(vBinary(txt).to_ical(), txt_ical)
|
||||
self.assertEqual(vBinary.from_ical(txt_ical), txt)
|
||||
|
||||
def test_prop_vBoolean(self):
|
||||
from ..prop import vBoolean
|
||||
|
||||
self.assertEqual(vBoolean(True).to_ical(), b'TRUE')
|
||||
self.assertEqual(vBoolean(0).to_ical(), b'FALSE')
|
||||
|
||||
# The roundtrip test
|
||||
self.assertEqual(vBoolean.from_ical(vBoolean(True).to_ical()), True)
|
||||
self.assertEqual(vBoolean.from_ical('true'), True)
|
||||
|
||||
def test_prop_vCalAddress(self):
|
||||
from ..prop import vCalAddress
|
||||
txt = b'MAILTO:maxm@mxm.dk'
|
||||
a = vCalAddress(txt)
|
||||
a.params['cn'] = 'Max M'
|
||||
|
||||
self.assertEqual(a.to_ical(), txt)
|
||||
self.assertIsInstance(a.params, Parameters)
|
||||
self.assertEqual(a.params, {'CN': 'Max M'})
|
||||
self.assertEqual(vCalAddress.from_ical(txt), 'MAILTO:maxm@mxm.dk')
|
||||
|
||||
def test_prop_vFloat(self):
|
||||
from ..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')
|
||||
|
||||
def test_prop_vInt(self):
|
||||
from ..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')
|
||||
|
||||
def test_prop_vDDDLists(self):
|
||||
from ..prop import vDDDLists
|
||||
|
||||
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')
|
||||
|
||||
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')
|
||||
|
||||
dt_list = vDDDLists([])
|
||||
self.assertEqual(dt_list.to_ical(), b'')
|
||||
|
||||
dt_list = vDDDLists([datetime(2000, 1, 1)])
|
||||
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')
|
||||
|
||||
def test_prop_vDDDTypes(self):
|
||||
from ..prop import vDDDTypes
|
||||
|
||||
self.assertTrue(isinstance(vDDDTypes.from_ical('20010101T123000'),
|
||||
datetime))
|
||||
|
||||
self.assertEqual(vDDDTypes.from_ical('20010101T123000Z'),
|
||||
datetime(2001, 1, 1, 12, 30, tzinfo=pytz.utc))
|
||||
|
||||
self.assertTrue(isinstance(vDDDTypes.from_ical('20010101'), date))
|
||||
|
||||
self.assertEqual(vDDDTypes.from_ical('P31D'), timedelta(31))
|
||||
|
||||
self.assertEqual(vDDDTypes.from_ical('-P31D'), timedelta(-31))
|
||||
|
||||
# Bad input
|
||||
self.assertRaises(ValueError, vDDDTypes, 42)
|
||||
|
||||
def test_prop_vDate(self):
|
||||
from ..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.from_ical('20010102'), date(2001, 1, 2))
|
||||
|
||||
self.assertRaises(ValueError, vDate, 'd')
|
||||
|
||||
def test_prop_vDatetime(self):
|
||||
from ..prop import vDatetime
|
||||
|
||||
dt = datetime(2001, 1, 1, 12, 30, 0)
|
||||
self.assertEqual(vDatetime(dt).to_ical(), b'20010101T123000')
|
||||
|
||||
self.assertEqual(vDatetime.from_ical('20000101T120000'),
|
||||
datetime(2000, 1, 1, 12, 0))
|
||||
|
||||
dutc = datetime(2001, 1, 1, 12, 30, 0, tzinfo=pytz.utc)
|
||||
self.assertEqual(vDatetime(dutc).to_ical(), b'20010101T123000Z')
|
||||
|
||||
dutc = datetime(1899, 1, 1, 12, 30, 0, tzinfo=pytz.utc)
|
||||
self.assertEqual(vDatetime(dutc).to_ical(), b'18990101T123000Z')
|
||||
|
||||
self.assertEqual(vDatetime.from_ical('20010101T000000'),
|
||||
datetime(2001, 1, 1, 0, 0))
|
||||
|
||||
self.assertRaises(ValueError, vDatetime.from_ical, '20010101T000000A')
|
||||
|
||||
utc = vDatetime.from_ical('20010101T000000Z')
|
||||
self.assertEqual(vDatetime(utc).to_ical(), b'20010101T000000Z')
|
||||
|
||||
# 1 minute before transition to DST
|
||||
dat = vDatetime.from_ical('20120311T015959', 'America/Denver')
|
||||
self.assertEqual(dat.strftime('%Y%m%d%H%M%S %z'),
|
||||
'20120311015959 -0700')
|
||||
|
||||
# After transition to DST
|
||||
dat = vDatetime.from_ical('20120311T030000', 'America/Denver')
|
||||
self.assertEqual(dat.strftime('%Y%m%d%H%M%S %z'),
|
||||
'20120311030000 -0600')
|
||||
|
||||
dat = vDatetime.from_ical('20101010T000000', 'Europe/Vienna')
|
||||
self.assertEqual(vDatetime(dat).to_ical(), b'20101010T000000')
|
||||
|
||||
def test_prop_vDuration(self):
|
||||
from ..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')
|
||||
|
||||
# How does the parsing work?
|
||||
self.assertEqual(vDuration.from_ical('PT1H0M22S'), timedelta(0, 3622))
|
||||
|
||||
self.assertRaises(ValueError, vDuration.from_ical, 'kox')
|
||||
|
||||
self.assertEqual(vDuration.from_ical('-P14D'), timedelta(-14))
|
||||
|
||||
self.assertRaises(ValueError, vDuration, 11)
|
||||
|
||||
def test_prop_vPeriod(self):
|
||||
from ..prop import vPeriod
|
||||
|
||||
# One day in exact datetimes
|
||||
per = (datetime(2000, 1, 1), datetime(2000, 1, 2))
|
||||
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')
|
||||
|
||||
# Roundtrip
|
||||
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)))
|
||||
|
||||
# Roundtrip with absolute time
|
||||
p = vPeriod.from_ical('20000101T000000Z/20000102T000000Z')
|
||||
self.assertEqual(vPeriod(p).to_ical(),
|
||||
b'20000101T000000Z/20000102T000000Z')
|
||||
|
||||
# And an error
|
||||
self.assertRaises(ValueError,
|
||||
vPeriod.from_ical, '20000101T000000/Psd31D')
|
||||
|
||||
# Timezoned
|
||||
dk = pytz.timezone('Europe/Copenhagen')
|
||||
start = datetime(2000, 1, 1, tzinfo=dk)
|
||||
end = datetime(2000, 1, 2, tzinfo=dk)
|
||||
per = (start, end)
|
||||
self.assertEqual(vPeriod(per).to_ical(),
|
||||
b'20000101T000000/20000102T000000')
|
||||
self.assertEqual(vPeriod(per).params['TZID'],
|
||||
'Europe/Copenhagen')
|
||||
|
||||
p = vPeriod((datetime(2000, 1, 1, tzinfo=dk), timedelta(days=31)))
|
||||
self.assertEqual(p.to_ical(), b'20000101T000000/P31D')
|
||||
|
||||
def test_prop_vWeekday(self):
|
||||
from ..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')
|
||||
|
||||
def test_prop_vFrequency(self):
|
||||
from ..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')
|
||||
|
||||
def test_prop_vRecur(self):
|
||||
from ..prop import vRecur
|
||||
|
||||
# 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
|
||||
})
|
||||
self.assertEqual(
|
||||
vRecur(r).to_ical(),
|
||||
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,
|
||||
})
|
||||
self.assertEqual(
|
||||
r.to_ical(),
|
||||
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', 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=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]}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
vRecur(r).to_ical(),
|
||||
b'FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=-SU;'
|
||||
b'BYMONTH=1'
|
||||
)
|
||||
|
||||
# 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')
|
||||
|
||||
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'
|
||||
)
|
||||
|
||||
# and some errors
|
||||
self.assertRaises(ValueError, vRecur.from_ical, 'BYDAY=12')
|
||||
|
||||
def test_prop_vText(self):
|
||||
from ..prop import vText
|
||||
|
||||
self.assertEqual(vText(u'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')
|
||||
|
||||
# Escaped newlines
|
||||
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 2445 support.
|
||||
t = vText(u'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'),
|
||||
u'Text ; with escaped, chars')
|
||||
|
||||
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'), u'Ol\ufffd')
|
||||
|
||||
# Notice how accented E character, encoded with latin-1, got replaced
|
||||
# with the official U+FFFD REPLACEMENT CHARACTER.
|
||||
|
||||
def test_prop_vTime(self):
|
||||
from ..prop import vTime
|
||||
|
||||
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')
|
||||
|
||||
def test_prop_vUri(self):
|
||||
from ..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/')
|
||||
|
||||
def test_prop_vGeo(self):
|
||||
from ..prop import vGeo
|
||||
|
||||
# Pass a list
|
||||
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')
|
||||
|
||||
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.assertRaises(ValueError, vGeo, 'g')
|
||||
|
||||
def test_prop_vUTCOffset(self):
|
||||
from ..prop import vUTCOffset
|
||||
|
||||
self.assertEqual(vUTCOffset(timedelta(hours=2)).to_ical(), '+0200')
|
||||
|
||||
self.assertEqual(vUTCOffset(timedelta(hours=-5)).to_ical(), '-0500')
|
||||
|
||||
self.assertEqual(vUTCOffset(timedelta()).to_ical(), '+0000')
|
||||
|
||||
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=1, minutes=30)).to_ical(),
|
||||
'+0130')
|
||||
|
||||
# Support seconds
|
||||
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(vUTCOffset.from_ical('+0230')).to_ical(),
|
||||
'+0230')
|
||||
|
||||
# And a few failures
|
||||
self.assertRaises(ValueError, vUTCOffset.from_ical, '+323k')
|
||||
|
||||
self.assertRaises(ValueError, vUTCOffset.from_ical, '+2400')
|
||||
|
||||
def test_prop_vInline(self):
|
||||
from ..prop import vInline
|
||||
|
||||
self.assertEqual(vInline('Some text'), 'Some text')
|
||||
self.assertEqual(vInline.from_ical('Some text'), 'Some text')
|
||||
|
||||
t2 = vInline('other text')
|
||||
t2.params['cn'] = 'Test Osterone'
|
||||
self.assertIsInstance(t2.params, Parameters)
|
||||
self.assertEqual(t2.params, {'CN': 'Test Osterone'})
|
||||
|
||||
def test_prop_TypesFactory(self):
|
||||
from ..prop import TypesFactory
|
||||
|
||||
# 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')
|
||||
|
||||
# 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))
|
||||
|
||||
# It can also be used to directly encode property and parameter values
|
||||
self.assertEqual(
|
||||
factory.to_ical('comment', u'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', u'Rasmussen, Max M\xfcller'),
|
||||
b'Rasmussen\\, Max M\xc3\xbcller')
|
||||
self.assertEqual(
|
||||
factory.from_ical('cn', b'Rasmussen\\, Max M\xc3\xb8ller'),
|
||||
u'Rasmussen, Max M\xf8ller'
|
||||
)
|
||||
|
||||
|
||||
class TestPropertyValues(unittest.TestCase):
|
||||
|
||||
def test_vDDDLists_timezone(self):
|
||||
"""Test vDDDLists with timezone information.
|
||||
"""
|
||||
from .. import Event
|
||||
vevent = Event()
|
||||
at = pytz.timezone('Europe/Vienna')
|
||||
dt1 = at.localize(datetime(2013, 1, 1))
|
||||
dt2 = at.localize(datetime(2013, 1, 2))
|
||||
dt3 = at.localize(datetime(2013, 1, 3))
|
||||
vevent.add('rdate', [dt1, dt2])
|
||||
vevent.add('exdate', dt3)
|
||||
ical = vevent.to_ical()
|
||||
|
||||
self.assertTrue(
|
||||
b'RDATE;TZID=Europe/Vienna:20130101T000000,20130102T000000' in ical
|
||||
)
|
||||
self.assertTrue(b'EXDATE;TZID=Europe/Vienna:20130103T000000' in ical)
|
@ -0,0 +1,28 @@
|
||||
from icalendar.tests import unittest
|
||||
from icalendar.tools import UIDGenerator
|
||||
|
||||
|
||||
class TestTools(unittest.TestCase):
|
||||
|
||||
def test_tools_UIDGenerator(self):
|
||||
|
||||
# Automatic semi-random uid
|
||||
g = UIDGenerator()
|
||||
uid = g.uid()
|
||||
|
||||
txt = uid.to_ical()
|
||||
length = 15 + 1 + 16 + 1 + 11
|
||||
self.assertTrue(len(txt) == length)
|
||||
self.assertTrue(b'@example.com' in txt)
|
||||
|
||||
# You should at least insert your own hostname to be more compliant
|
||||
uid = g.uid('Example.ORG')
|
||||
txt = uid.to_ical()
|
||||
self.assertTrue(len(txt) == length)
|
||||
self.assertTrue(b'@Example.ORG' in txt)
|
||||
|
||||
# You can also insert a path or similar
|
||||
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)
|
3
libs/icalendar-3.6.1/build/lib/icalendar/tests/time.ics
Normal file
3
libs/icalendar-3.6.1/build/lib/icalendar/tests/time.ics
Normal file
@ -0,0 +1,3 @@
|
||||
BEGIN:VCALENDAR
|
||||
X-SOMETIME;VALUE=TIME:172010
|
||||
END:VCALENDAR
|
36
libs/icalendar-3.6.1/build/lib/icalendar/tests/timezoned.ics
Normal file
36
libs/icalendar-3.6.1/build/lib/icalendar/tests/timezoned.ics
Normal file
@ -0,0 +1,36 @@
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Plone.org//NONSGML plone.app.event//EN
|
||||
VERSION:2.0
|
||||
X-WR-CALNAME:test create calendar
|
||||
X-WR-CALDESC:icalendar test
|
||||
X-WR-RELCALID:12345
|
||||
X-WR-TIMEZONE:Europe/Vienna
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/Vienna
|
||||
X-LIC-LOCATION:Europe/Vienna
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0200
|
||||
TZNAME:CEST
|
||||
DTSTART:19700329T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0200
|
||||
TZOFFSETTO:+0100
|
||||
TZNAME:CET
|
||||
DTSTART:19701025T030000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
DTSTART;TZID=Europe/Vienna:20120213T100000
|
||||
DTEND;TZID=Europe/Vienna:20120217T180000
|
||||
DTSTAMP:20101010T091010Z
|
||||
CREATED:20101010T091010Z
|
||||
UID:123456
|
||||
SUMMARY:artsprint 2012
|
||||
DESCRIPTION:sprinting at the artsprint
|
||||
LOCATION:aka bild, wien
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
33
libs/icalendar-3.6.1/build/lib/icalendar/tools.py
Normal file
33
libs/icalendar-3.6.1/build/lib/icalendar/tools.py
Normal file
@ -0,0 +1,33 @@
|
||||
from datetime import datetime
|
||||
from icalendar.parser_tools import to_unicode
|
||||
from icalendar.prop import vDatetime
|
||||
from icalendar.prop import vText
|
||||
from string import ascii_letters
|
||||
from string import digits
|
||||
|
||||
import random
|
||||
|
||||
|
||||
class UIDGenerator(object):
|
||||
"""If you are too lazy to create real uid's.
|
||||
|
||||
"""
|
||||
chars = list(ascii_letters + digits)
|
||||
|
||||
def rnd_string(self, length=16):
|
||||
"""Generates a string with random characters of length.
|
||||
"""
|
||||
return ''.join([random.choice(self.chars) for _ in range(length)])
|
||||
|
||||
def uid(self, host_name='example.com', unique=''):
|
||||
"""Generates a unique id consisting of:
|
||||
datetime-uniquevalue@host.
|
||||
Like:
|
||||
20050105T225746Z-HKtJMqUgdO0jDUwm@example.com
|
||||
"""
|
||||
host_name = to_unicode(host_name)
|
||||
unique = unique or self.rnd_string()
|
||||
today = to_unicode(vDatetime(datetime.today()).to_ical())
|
||||
return vText('%s-%s@%s' % (today,
|
||||
unique,
|
||||
host_name))
|
BIN
libs/icalendar-3.6.1/dist/icalendar-3.6.1-py3.3.egg
vendored
Normal file
BIN
libs/icalendar-3.6.1/dist/icalendar-3.6.1-py3.3.egg
vendored
Normal file
Binary file not shown.
130
libs/icalendar-3.6.1/docs/Makefile
Normal file
130
libs/icalendar-3.6.1/docs/Makefile
Normal file
@ -0,0 +1,130 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/icalendar.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/icalendar.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/icalendar"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/icalendar"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
make -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
14
libs/icalendar-3.6.1/docs/_themes/icalendar/layout.html
vendored
Normal file
14
libs/icalendar-3.6.1/docs/_themes/icalendar/layout.html
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
{#
|
||||
sphinxdoc/layout.html
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sphinx layout template for the sphinxdoc theme.
|
||||
|
||||
:copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
#}
|
||||
{% extends "basic/layout.html" %}
|
||||
|
||||
{# put the sidebar before the body #}
|
||||
{% block sidebar1 %}{{ sidebar() }}{% endblock %}
|
||||
{% block sidebar2 %}{% endblock %}
|
428
libs/icalendar-3.6.1/docs/_themes/icalendar/static/icalendar.css
vendored
Normal file
428
libs/icalendar-3.6.1/docs/_themes/icalendar/static/icalendar.css
vendored
Normal file
@ -0,0 +1,428 @@
|
||||
/*
|
||||
* sphinxdoc.css_t
|
||||
* ~~~~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- sphinxdoc theme. Originally created by
|
||||
* Armin Ronacher for Werkzeug.
|
||||
*
|
||||
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif, serif;
|
||||
font-size: 15px;
|
||||
letter-spacing: -0.05em;
|
||||
line-height: 1.3em;
|
||||
background-color: #F5F4EF;
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
width: 950px;
|
||||
-moz-box-shadow: 0px 0px 15px #bbb;
|
||||
-webkit-box-shadow: 0px 0px 15px #bbb;
|
||||
box-shadow: 0px 0px 15px #bbb;
|
||||
}
|
||||
|
||||
div.document {
|
||||
background-color: white;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 240px 0 0;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.body {
|
||||
margin: 0;
|
||||
padding: 0.5em 20px 20px 20px;
|
||||
}
|
||||
|
||||
|
||||
/*Banner*/
|
||||
#banner {
|
||||
height: 100px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#vlinux-logo {
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#nav {
|
||||
background: #48443d url('menu_bg.png') repeat-x;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#nav li {
|
||||
display: inline;
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#nav a:link, #nav a:visited {
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
padding: 5px 20px;
|
||||
height: 20px;
|
||||
text-decoration: none;
|
||||
font-size: 0.8em;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#nav a:hover, #nav a:active, #nav .active a:link, #nav .active a:visited {
|
||||
background: #89c912;
|
||||
color: #fff;
|
||||
text-shadow: #666 2px 2px 2px;
|
||||
}
|
||||
|
||||
|
||||
div.related {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
/*background-image: url(navigation.png);*/
|
||||
background: #fafafa;
|
||||
height: 2em;
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
div.related ul li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 2em;
|
||||
float: left;
|
||||
}
|
||||
|
||||
div.related ul li.right {
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
div.related ul li a {
|
||||
margin: 0;
|
||||
padding: 0 5px 0 5px;
|
||||
line-height: 1.75em;
|
||||
color: #48443d;
|
||||
border-bottom: #48443d 1px dotted;
|
||||
}
|
||||
|
||||
div.related ul li a:hover {
|
||||
color: #89c912;
|
||||
text-decoration: none;
|
||||
border-bottom: #89c912 1px dotted;
|
||||
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
margin: 0;
|
||||
padding: 0.5em 15px 15px 0;
|
||||
width: 210px;
|
||||
float: right;
|
||||
font-size: 0.8em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3, div.sphinxsidebar h4 {
|
||||
margin: 1em 0;
|
||||
font-size: 1.8em;
|
||||
line-height: 1.3em;
|
||||
padding: 0.2em 0.5em;
|
||||
color: white;
|
||||
/*border: 1px solid #86989B;*/
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
padding-left: 1.5em;
|
||||
margin-top: 7px;
|
||||
padding: 0;
|
||||
line-height: 130%;
|
||||
font-size: 14px;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul ul {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
display: none;
|
||||
background-color: #FAFAFA;
|
||||
border-bottom: 1px solid #DDDDDD;
|
||||
border-top: 1px solid #DDDDDD;
|
||||
clear: both;
|
||||
height: 2em;
|
||||
line-height: 1.75em;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.footer a,
|
||||
div.footer a:hover {
|
||||
border-bottom: 1px dotted #48443D;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
p {
|
||||
margin: 0.8em 0 0.5em 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #48443d;
|
||||
text-decoration: none;
|
||||
border-bottom: #48443d 1px dotted;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #89c912;
|
||||
text-decoration: none;
|
||||
border-bottom: #89c912 1px dotted;
|
||||
}
|
||||
|
||||
/*div.body a {
|
||||
text-decoration: underline;
|
||||
}*/
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
padding: 0.7em 0 0.3em 0;
|
||||
font-size: 2.5em;
|
||||
line-height: 1em;
|
||||
color: #48443d;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 1.3em 0 0.2em 0;
|
||||
font-size: 2em;
|
||||
line-height: 0.8em;
|
||||
padding: 0;
|
||||
color: #48443d;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 1em 0 -0.3em 0;
|
||||
font-size: 1.5em;
|
||||
color: #48443d;
|
||||
}
|
||||
|
||||
div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a {
|
||||
color: #48443d;
|
||||
}
|
||||
|
||||
h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {
|
||||
display: none;
|
||||
margin: 0 0 0 0.3em;
|
||||
padding: 0 0.2em 0 0.2em;
|
||||
color: #aaa!important;
|
||||
}
|
||||
|
||||
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,
|
||||
h5:hover a.anchor, h6:hover a.anchor {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,
|
||||
h5 a.anchor:hover, h6 a.anchor:hover {
|
||||
color: #777;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
a.headerlink {
|
||||
color: #c60f0f!important;
|
||||
font-size: 1em;
|
||||
margin-left: 6px;
|
||||
padding: 0 4px 0 4px;
|
||||
text-decoration: none!important;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
background-color: #ccc;
|
||||
color: white!important;
|
||||
}
|
||||
|
||||
cite, code, tt {
|
||||
font-family: 'Consolas', 'Deja Vu Sans Mono',
|
||||
'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.95em;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #f2f2f2;
|
||||
border-bottom: 1px solid #ddd;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname, tt.xref {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
display: none;
|
||||
}
|
||||
.body hr {
|
||||
border: 1px solid #abc;
|
||||
margin: 2em;
|
||||
}
|
||||
|
||||
a tt {
|
||||
border: 0;
|
||||
color: #CA7900;
|
||||
}
|
||||
|
||||
a tt:hover {
|
||||
color: #2491CF;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: 'Consolas', 'Deja Vu Sans Mono',
|
||||
'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.95em;
|
||||
letter-spacing: 0.015em;
|
||||
line-height: 120%;
|
||||
padding: 0.5em;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
pre a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
td.linenos pre {
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
div.quotebar {
|
||||
background-color: #f8f8f8;
|
||||
max-width: 250px;
|
||||
float: right;
|
||||
padding: 2px 7px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
margin: 0 -0.5em 0 -0.5em;
|
||||
}
|
||||
|
||||
table td, table th {
|
||||
padding: 0.2em 0.5em 0.2em 0.5em;
|
||||
}
|
||||
|
||||
div.admonition, div.warning {
|
||||
font-size: 0.9em;
|
||||
margin: 1em 0 1em 0;
|
||||
border: 1px solid #86989B;
|
||||
background-color: #f7f7f7;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.admonition p, div.warning p {
|
||||
margin: 0.5em 1em 0.5em 1em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.admonition pre, div.warning pre {
|
||||
margin: 0.4em 1em 0.4em 1em;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title,
|
||||
div.warning p.admonition-title {
|
||||
margin: 0;
|
||||
padding: 0.1em 0 0.1em 0.5em;
|
||||
color: white;
|
||||
border-bottom: 1px solid #86989B;
|
||||
font-weight: bold;
|
||||
background-color: #AFC1C4;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
border: 1px solid #940000;
|
||||
}
|
||||
|
||||
div.warning p.admonition-title {
|
||||
background-color: #CF0000;
|
||||
border-bottom-color: #940000;
|
||||
}
|
||||
|
||||
div.admonition ul, div.admonition ol,
|
||||
div.warning ul, div.warning ol {
|
||||
margin: 0.1em 0.5em 0.5em 3em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.versioninfo {
|
||||
margin: 1em 0 0 0;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #DDEAF0;
|
||||
padding: 8px;
|
||||
line-height: 1.3em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
||||
'Verdana', sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
background-color: #f4debf;
|
||||
border-top: 1px solid #ac9;
|
||||
border-bottom: 1px solid #ac9;
|
||||
}
|
||||
|
||||
|
||||
/* custom */
|
||||
#twitter a {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#twitter a:hover {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.twtr-widget {
|
||||
font-family: font-family: 'Droid Sans', serif !important;
|
||||
}
|
||||
|
||||
#disqus_thread {
|
||||
padding: 0.5em 20px 20px 20px;
|
||||
width: 670px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
p#comments {
|
||||
padding: 0.5em 20px 20px 20px;
|
||||
}
|
4
libs/icalendar-3.6.1/docs/_themes/icalendar/theme.conf
vendored
Normal file
4
libs/icalendar-3.6.1/docs/_themes/icalendar/theme.conf
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = icalendar.css
|
||||
pygments_style = friendly
|
14
libs/icalendar-3.6.1/docs/about.rst
Normal file
14
libs/icalendar-3.6.1/docs/about.rst
Normal file
@ -0,0 +1,14 @@
|
||||
About
|
||||
=====
|
||||
|
||||
`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.
|
||||
|
||||
.. _`Max M`: http://www.mxm.dk
|
1
libs/icalendar-3.6.1/docs/changelog.rst
Normal file
1
libs/icalendar-3.6.1/docs/changelog.rst
Normal file
@ -0,0 +1 @@
|
||||
.. include:: ../CHANGES.rst
|
221
libs/icalendar-3.6.1/docs/conf.py
Normal file
221
libs/icalendar-3.6.1/docs/conf.py
Normal file
@ -0,0 +1,221 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# icalendar documentation build configuration file, created by
|
||||
# sphinx-quickstart on Wed Aug 17 00:40:41 2011.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
# templates_path = []
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'icalendar'
|
||||
copyright = u'2011, MaxM'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '3.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '3.0dev'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'icalendar'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
html_theme_path = ['_themes']
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
#html_static_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
html_sidebars = {
|
||||
'**': [
|
||||
'globaltoc.html',
|
||||
'searchbox.html',
|
||||
],
|
||||
}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'icalendardoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'icalendar.tex', u'icalendar Documentation',
|
||||
u'MaxM', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'icalendar', u'icalendar Documentation',
|
||||
[u'MaxM'], 1)
|
||||
]
|
37
libs/icalendar-3.6.1/docs/credits.rst
Normal file
37
libs/icalendar-3.6.1/docs/credits.rst
Normal file
@ -0,0 +1,37 @@
|
||||
icalendar contributors
|
||||
======================
|
||||
|
||||
- Johannes Raggam <johannes@raggam.co.at> (Maintainer)
|
||||
- Max M <maxm@mxm.dk> (Original author)
|
||||
- Andreas Zeidler <az@zitc.de>
|
||||
- Andrey Nikolaev <nikolaeff@gmail.com>
|
||||
- Barak Michener <me@barakmich.com>
|
||||
- Christophe de Vienne <cdevienne@gmail.com>
|
||||
- Christian Geier <contact@lostpackets.de>
|
||||
- Dai MIKURUBE <dmikurube@acm.org>
|
||||
- Dan Stovall <dbstovall@gmail.com>
|
||||
- Eric Hanchrow <erich@cozi.com>
|
||||
- Erik Simmler <tgecho@gmail.com>
|
||||
- George V. Reilly <george@reilly.org>
|
||||
- Jannis Leidel <jannis@leidel.info>
|
||||
- Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>
|
||||
- Lennart Regebro <lregebro@nuxeo.com>
|
||||
- Marc Egli <frog32@me.com>
|
||||
- Martijn Faassen <faassen@infrae.com>
|
||||
- Martin Melin <git@martinmelin.com>
|
||||
- Michael Smith <msmith@fluendo.com>
|
||||
- Mikael Frykholm <mikael@frykholm.com>
|
||||
- Olivier Grisel <ogrisel@nuxeo.com>
|
||||
- Pavel Repin <prepin@gmail.com>
|
||||
- Pedro Ferreira <jose.pedro.ferreira@cern.ch>
|
||||
- Rembane <andeke@gmail.com>
|
||||
- Robert Niederreiter <rnix@squarewave.at>
|
||||
- Rok Garbas <rok@garbas.si>
|
||||
- Ronan Dunklau <ronan@dunklau.fr>
|
||||
- Sidnei da Silva <sidnei@enfoldsystems.com>
|
||||
- Stanislav Ochotnicky <sochotnicky@redhat.com>
|
||||
- Stefan Schwarzer <sschwarzer@sschwarzer.net>
|
||||
- Victor Varvaryuk <victor.varvariuc@gmail.com>
|
||||
- Wichert Akkerman <wichert@wiggy.net>
|
||||
- spanktar <spanky@kapanka.com>
|
||||
- tgecho <tgecho@gmail.com>
|
16
libs/icalendar-3.6.1/docs/index.rst
Normal file
16
libs/icalendar-3.6.1/docs/index.rst
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
.. include:: ../README.rst
|
||||
|
||||
Contents
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
about
|
||||
install
|
||||
usage
|
||||
RFC 5545 <rfc5545/index>
|
||||
changelog
|
||||
credits
|
||||
license
|
11
libs/icalendar-3.6.1/docs/install.rst
Normal file
11
libs/icalendar-3.6.1/docs/install.rst
Normal file
@ -0,0 +1,11 @@
|
||||
Installing iCalendar
|
||||
====================
|
||||
|
||||
To install the icalendar package, use::
|
||||
|
||||
python setup.py install
|
||||
|
||||
If installation is successful, you be able to import the iCalendar
|
||||
package, like this::
|
||||
|
||||
>>> import icalendar
|
1
libs/icalendar-3.6.1/docs/license.rst
Normal file
1
libs/icalendar-3.6.1/docs/license.rst
Normal file
@ -0,0 +1 @@
|
||||
.. include:: ../LICENSE.rst
|
324
libs/icalendar-3.6.1/docs/usage.rst
Normal file
324
libs/icalendar-3.6.1/docs/usage.rst
Normal file
@ -0,0 +1,324 @@
|
||||
iCalendar package
|
||||
=================
|
||||
|
||||
This package is used for parsing and generating iCalendar files following the
|
||||
standard in RFC 2445.
|
||||
|
||||
It should be fully compliant, but it is possible to generate and parse invalid
|
||||
files if you really want to.
|
||||
|
||||
|
||||
File structure
|
||||
--------------
|
||||
|
||||
An iCalendar file is a text file (utf-8) with a special format. Basically it
|
||||
consists of content lines.
|
||||
|
||||
Each content line defines a property that has 3 parts (name, parameters,
|
||||
values). Parameters are optional.
|
||||
|
||||
A simple content line with only name and value could look like this::
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
|
||||
A content line with parameters can look like this::
|
||||
|
||||
ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:example@example.com
|
||||
|
||||
And the parts are::
|
||||
|
||||
Name: ATTENDEE
|
||||
Params: CN=Max Rasmussen;ROLE=REQ-PARTICIPANT
|
||||
Value: MAILTO:example@example.com
|
||||
|
||||
Long content lines are usually "folded" to less than 75 character, but the
|
||||
package takes care of that.
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
On a higher level iCalendar files consists of components. Components can have
|
||||
sub components.
|
||||
|
||||
The root component is the VCALENDAR::
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
... vcalendar properties ...
|
||||
END:VCALENDAR
|
||||
|
||||
The most frequent subcomponent to a VCALENDAR is a VEVENT. They are
|
||||
nested like this::
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
... vcalendar properties ...
|
||||
BEGIN:VEVENT
|
||||
... vevent properties ...
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
Inside the components there are properties with values. The values
|
||||
have special types. like integer, text, datetime etc. These values are
|
||||
encoded in a special text format in an iCalendar file.
|
||||
|
||||
There are methods for converting to and from these encodings in the package.
|
||||
|
||||
These are the most important imports::
|
||||
|
||||
>>> from icalendar import Calendar, Event
|
||||
|
||||
|
||||
Components
|
||||
----------
|
||||
|
||||
Components are like (Case Insensitive) dicts. So if you want to set a property
|
||||
you do it like this. The calendar is a component::
|
||||
|
||||
>>> cal = Calendar()
|
||||
>>> cal['dtstart'] = '20050404T080000'
|
||||
>>> cal['summary'] = 'Python meeting about calendaring'
|
||||
>>> for k,v in cal.items():
|
||||
... k,v
|
||||
(u'DTSTART', '20050404T080000')
|
||||
(u'SUMMARY', 'Python meeting about calendaring')
|
||||
|
||||
NOTE: the recommended way to add components to the calendar is to use
|
||||
create the subcomponent and add it via Calendar.add! The example above adds a
|
||||
string, but not a vText component.
|
||||
|
||||
|
||||
You can generate a string for a file with the to_ical() method::
|
||||
|
||||
>>> cal.to_ical()
|
||||
'BEGIN:VCALENDAR\r\nDTSTART:20050404T080000\r\nSUMMARY:Python meeting about calendaring\r\nEND:VCALENDAR\r\n'
|
||||
|
||||
The rendered view is easier to read::
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
DTSTART:20050404T080000
|
||||
SUMMARY:Python meeting about calendaring
|
||||
END:VCALENDAR
|
||||
|
||||
So, let's define a function so we can easily display to_ical() output::
|
||||
|
||||
>>> def display(cal):
|
||||
... return cal.to_ical().replace('\r\n', '\n').strip()
|
||||
|
||||
You can set multiple properties like this::
|
||||
|
||||
>>> cal = Calendar()
|
||||
>>> cal['attendee'] = ['MAILTO:maxm@mxm.dk','MAILTO:test@example.com']
|
||||
>>> print display(cal)
|
||||
BEGIN:VCALENDAR
|
||||
ATTENDEE:MAILTO:maxm@mxm.dk
|
||||
ATTENDEE:MAILTO:test@example.com
|
||||
END:VCALENDAR
|
||||
|
||||
If you don't want to care about whether a property value is a list or
|
||||
a single value, just use the add() method. It will automatically
|
||||
convert the property to a list of values if more than one value is
|
||||
added. Here is an example::
|
||||
|
||||
>>> cal = Calendar()
|
||||
>>> cal.add('attendee', 'MAILTO:maxm@mxm.dk')
|
||||
>>> cal.add('attendee', 'MAILTO:test@example.com')
|
||||
>>> print display(cal)
|
||||
BEGIN:VCALENDAR
|
||||
ATTENDEE:MAILTO:maxm@mxm.dk
|
||||
ATTENDEE:MAILTO:test@example.com
|
||||
END:VCALENDAR
|
||||
|
||||
Note: this version doesn't check for compliance, so you should look in
|
||||
the RFC 2445 spec for legal properties for each component, or look in
|
||||
the icalendar/calendar.py file, where it is at least defined for each
|
||||
component.
|
||||
|
||||
|
||||
Subcomponents
|
||||
-------------
|
||||
|
||||
Any component can have subcomponents. Eg. inside a calendar there can
|
||||
be events. They can be arbitrarily nested. First by making a new
|
||||
component::
|
||||
|
||||
>>> event = Event()
|
||||
>>> event['uid'] = '42'
|
||||
>>> event['dtstart'] = '20050404T080000'
|
||||
|
||||
And then appending it to a "parent"::
|
||||
|
||||
>>> cal.add_component(event)
|
||||
>>> print display(cal)
|
||||
BEGIN:VCALENDAR
|
||||
ATTENDEE:MAILTO:maxm@mxm.dk
|
||||
ATTENDEE:MAILTO:test@example.com
|
||||
BEGIN:VEVENT
|
||||
DTSTART:20050404T080000
|
||||
UID:42
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
Subcomponents are appended to the subcomponents property on the component::
|
||||
|
||||
>>> cal.subcomponents
|
||||
[VEVENT({'DTSTART': '20050404T080000', 'UID': '42'})]
|
||||
|
||||
|
||||
Value types
|
||||
-----------
|
||||
|
||||
Property values are utf-8 encoded strings.
|
||||
|
||||
This is impractical if you want to use the data for further
|
||||
computation. Eg. the datetime format looks like this:
|
||||
'20050404T080000'. But the package makes it simple to Parse and
|
||||
generate iCalendar formatted strings.
|
||||
|
||||
Basically you can make the add() method do the thinking, or you can do it
|
||||
yourself.
|
||||
|
||||
To add a datetime value, you can use Pythons built in datetime types,
|
||||
and the set the encode parameter to true, and it will convert to the
|
||||
type defined in the spec::
|
||||
|
||||
>>> from datetime import datetime
|
||||
>>> cal.add('dtstart', datetime(2005,4,4,8,0,0))
|
||||
>>> cal['dtstart'].to_ical()
|
||||
'20050404T080000'
|
||||
|
||||
If that doesn't work satisfactorily for some reason, you can also do it
|
||||
manually.
|
||||
|
||||
In 'icalendar.prop', all the iCalendar data types are defined. Each
|
||||
type has a class that can parse and encode the type.
|
||||
|
||||
So if you want to do it manually::
|
||||
|
||||
>>> from icalendar import vDatetime
|
||||
>>> now = datetime(2005,4,4,8,0,0)
|
||||
>>> vDatetime(now).to_ical()
|
||||
'20050404T080000'
|
||||
|
||||
So the drill is to initialise the object with a python built in type,
|
||||
and then call the "to_ical()" method on the object. That will return an
|
||||
ical encoded string.
|
||||
|
||||
You can do it the other way around too. To parse an encoded string, just call
|
||||
the "from_ical()" method, and it will return an instance of the corresponding
|
||||
Python type::
|
||||
|
||||
>>> vDatetime.from_ical('20050404T080000')
|
||||
datetime.datetime(2005, 4, 4, 8, 0)
|
||||
|
||||
>>> dt = vDatetime.from_ical('20050404T080000Z')
|
||||
>>> repr(dt)[:62]
|
||||
'datetime.datetime(2005, 4, 4, 8, 0, tzinfo=<UTC>)'
|
||||
|
||||
You can also choose to use the decoded() method, which will return a decoded
|
||||
value directly::
|
||||
|
||||
>>> cal = Calendar()
|
||||
>>> cal.add('dtstart', datetime(2005,4,4,8,0,0))
|
||||
>>> cal['dtstart'].to_ical()
|
||||
'20050404T080000'
|
||||
>>> cal.decoded('dtstart')
|
||||
datetime.datetime(2005, 4, 4, 8, 0)
|
||||
|
||||
|
||||
Property parameters
|
||||
-------------------
|
||||
|
||||
Property parameters are automatically added, depending on the input value. For
|
||||
example, for date/time related properties, the value type and timezone
|
||||
identifier (if applicable) are automatically added here::
|
||||
|
||||
>>> event = Event()
|
||||
>>> event.add('dtstart', datetime(2010, 10, 10, 10, 0, 0,
|
||||
... tzinfo=pytz.timezone("Europe/Vienna")))
|
||||
|
||||
>>> lines = event.to_ical().splitlines()
|
||||
>>> self.assertTrue(
|
||||
... b"DTSTART;TZID=Europe/Vienna;VALUE=DATE-TIME:20101010T100000"
|
||||
... in lines)
|
||||
|
||||
|
||||
You can also add arbitrary property parameters by passing a parameters
|
||||
dictionary to the add method like so::
|
||||
|
||||
>>> event = Event()
|
||||
>>> event.add('X-TEST-PROP', 'tryout.',
|
||||
.... parameters={'prop1': 'val1', 'prop2': 'val2'})
|
||||
>>> lines = event.to_ical().splitlines()
|
||||
>>> self.assertTrue(b"X-TEST-PROP;PROP1=val1;PROP2=val2:tryout." in lines)
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Here is an example generating a complete iCal calendar file with a
|
||||
single event that can be loaded into the Mozilla calendar
|
||||
|
||||
Init the calendar::
|
||||
|
||||
>>> cal = Calendar()
|
||||
>>> from datetime import datetime
|
||||
|
||||
Some properties are required to be compliant::
|
||||
|
||||
>>> cal.add('prodid', '-//My calendar product//mxm.dk//')
|
||||
>>> cal.add('version', '2.0')
|
||||
|
||||
We need at least one subcomponent for a calendar to be compliant::
|
||||
|
||||
>>> import pytz
|
||||
>>> event = Event()
|
||||
>>> event.add('summary', 'Python meeting about calendaring')
|
||||
>>> event.add('dtstart', datetime(2005,4,4,8,0,0,tzinfo=pytz.utc))
|
||||
>>> event.add('dtend', datetime(2005,4,4,10,0,0,tzinfo=pytz.utc))
|
||||
>>> event.add('dtstamp', datetime(2005,4,4,0,10,0,tzinfo=pytz.utc))
|
||||
|
||||
A property with parameters. Notice that they are an attribute on the value::
|
||||
|
||||
>>> from icalendar import vCalAddress, vText
|
||||
>>> organizer = vCalAddress('MAILTO:noone@example.com')
|
||||
|
||||
Automatic encoding is not yet implemented for parameter values, so you
|
||||
must use the 'v*' types you can import from the icalendar package
|
||||
(they're defined in ``icalendar.prop``)::
|
||||
|
||||
>>> organizer.params['cn'] = vText('Max Rasmussen')
|
||||
>>> organizer.params['role'] = vText('CHAIR')
|
||||
>>> event['organizer'] = organizer
|
||||
>>> event['location'] = vText('Odense, Denmark')
|
||||
|
||||
>>> event['uid'] = '20050115T101010/27346262376@mxm.dk'
|
||||
>>> event.add('priority', 5)
|
||||
|
||||
>>> attendee = vCalAddress('MAILTO:maxm@example.com')
|
||||
>>> attendee.params['cn'] = vText('Max Rasmussen')
|
||||
>>> attendee.params['ROLE'] = vText('REQ-PARTICIPANT')
|
||||
>>> event.add('attendee', attendee, encode=0)
|
||||
|
||||
>>> attendee = vCalAddress('MAILTO:the-dude@example.com')
|
||||
>>> attendee.params['cn'] = vText('The Dude')
|
||||
>>> attendee.params['ROLE'] = vText('REQ-PARTICIPANT')
|
||||
>>> event.add('attendee', attendee, encode=0)
|
||||
|
||||
Add the event to the calendar::
|
||||
|
||||
>>> cal.add_component(event)
|
||||
|
||||
Write to disk::
|
||||
|
||||
>>> import tempfile, os
|
||||
>>> directory = tempfile.mkdtemp()
|
||||
>>> f = open(os.path.join(directory, 'example.ics'), 'wb')
|
||||
>>> f.write(cal.to_ical())
|
||||
>>> f.close()
|
||||
|
||||
|
||||
More documentation
|
||||
==================
|
||||
|
||||
Have a look at the tests of this package to get more examples.
|
||||
All modules and classes docstrings, which document how they work.
|
5
libs/icalendar-3.6.1/setup.cfg
Normal file
5
libs/icalendar-3.6.1/setup.cfg
Normal file
@ -0,0 +1,5 @@
|
||||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
tag_svn_revision = 0
|
||||
|
53
libs/icalendar-3.6.1/setup.py
Normal file
53
libs/icalendar-3.6.1/setup.py
Normal file
@ -0,0 +1,53 @@
|
||||
import codecs
|
||||
import setuptools
|
||||
import sys
|
||||
|
||||
|
||||
version = '3.6.1'
|
||||
shortdesc = 'iCalendar parser/generator'
|
||||
longdesc = codecs.open('README.rst', encoding='utf-8').read()
|
||||
longdesc += codecs.open('CHANGES.rst', encoding='utf-8').read()
|
||||
longdesc += codecs.open('LICENSE.rst', encoding='utf-8').read()
|
||||
|
||||
|
||||
tests_require = []
|
||||
if sys.version_info[:2] == (2, 6):
|
||||
# Python unittest2 only needed for Python 2.6
|
||||
tests_require = ['unittest2']
|
||||
|
||||
|
||||
setuptools.setup(
|
||||
name='icalendar',
|
||||
version=version,
|
||||
description=shortdesc,
|
||||
long_description=longdesc,
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 2.6",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.3",
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
],
|
||||
keywords='calendar calendaring ical icalendar event todo journal '
|
||||
'recurring',
|
||||
author='Plone Foundation',
|
||||
author_email='plone-developers@lists.sourceforge.net',
|
||||
url='https://github.com/collective/icalendar',
|
||||
license='BSD',
|
||||
packages=setuptools.find_packages('src'),
|
||||
package_dir={'': 'src'},
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=[
|
||||
'setuptools',
|
||||
'python-dateutil',
|
||||
'pytz',
|
||||
],
|
||||
extras_require={
|
||||
'test': tests_require
|
||||
}
|
||||
)
|
534
libs/icalendar-3.6.1/src/icalendar.egg-info/PKG-INFO
Normal file
534
libs/icalendar-3.6.1/src/icalendar.egg-info/PKG-INFO
Normal file
@ -0,0 +1,534 @@
|
||||
Metadata-Version: 1.1
|
||||
Name: icalendar
|
||||
Version: 3.6.1
|
||||
Summary: iCalendar parser/generator
|
||||
Home-page: https://github.com/collective/icalendar
|
||||
Author: Plone Foundation
|
||||
Author-email: plone-developers@lists.sourceforge.net
|
||||
License: BSD
|
||||
Description: ==========================================================
|
||||
Internet Calendaring and Scheduling (iCalendar) for Python
|
||||
==========================================================
|
||||
|
||||
The `icalendar`_ package is a parser/generator of iCalendar files for use
|
||||
with Python.
|
||||
|
||||
----
|
||||
|
||||
:Homepage: http://icalendar.readthedocs.org
|
||||
:Code: http://github.com/collective/icalendar
|
||||
:Mailing list: http://github.com/collective/icalendar/issues
|
||||
:Dependencies: `setuptools`_ and since version 3.0 we depend on `pytz`_.
|
||||
:Compatible with: Python 2.6, 2.7 and 3.3+
|
||||
:License: `BSD`_
|
||||
|
||||
----
|
||||
|
||||
|
||||
Roadmap
|
||||
=======
|
||||
|
||||
- 3.6: Python 3 support (current version)
|
||||
|
||||
- 4.0: API refactoring
|
||||
|
||||
|
||||
|
||||
Changes in version 3.0
|
||||
======================
|
||||
|
||||
API Change
|
||||
----------
|
||||
|
||||
Since version we unified to icalendar de/serialization API to use only to_ical
|
||||
(for writing an ical string from the internal representation) and from_ical
|
||||
(for parsing an ical string into the internal representation).
|
||||
|
||||
to_ical is now used instead of the methods ical, string, as_string and instead
|
||||
of string casting via __str__ and str.
|
||||
|
||||
from_ical is now used instead of from_string.
|
||||
|
||||
This change is a requirement for future Python 3 compatibility. Please update
|
||||
your code to reflect to the new API.
|
||||
|
||||
Timezone support
|
||||
----------------
|
||||
|
||||
Timezones are now fully supported in icalendar for serialization and
|
||||
deserialization. We use the pytz library for timezone components of datetime
|
||||
instances. The timezone identifiers must be valid pytz respectively Olson
|
||||
database timezone identifiers. This can be a problem for 'GMT' identifiers,
|
||||
which are not defined in the Olson database.
|
||||
|
||||
Instead of the own UTC tzinfo implementation we use pytz UTC tzinfo object now.
|
||||
|
||||
|
||||
About this fork which is not a fork anymore
|
||||
===========================================
|
||||
|
||||
Aim of this fork (not fork anymore, read further) was to bring this package up
|
||||
to date with latest icalendar `RFC`_ specification as part of
|
||||
`plone.app.event`_ project which goal is to bring recurrent evens to `Plone`_.
|
||||
|
||||
After some thoughts we (Plone developers involved with `plone.app.event`_) send
|
||||
a suggestion to icalendar-dev@codespeak.net to take over mainaining of
|
||||
`icalendar`_. Nobody object and since version 2.2 we are back to development.
|
||||
|
||||
.. _`icalendar`: http://pypi.python.org/pypi/icalendar
|
||||
.. _`plone.app.event`: http://github.com/plone/plone.app.event
|
||||
.. _`Plone`: http://plone.org
|
||||
.. _`pytz`: http://pypi.python.org/pypi/pytz
|
||||
.. _`setuptools`: http://pypi.python.org/pypi/setuptools
|
||||
.. _`RFC`: http://www.ietf.org/rfc/rfc5545.txt
|
||||
.. _`BSD`: https://github.com/collective/icalendar/issues/2
|
||||
|
||||
|
||||
Test Coverage Report
|
||||
====================
|
||||
|
||||
Output from coverage test::
|
||||
|
||||
Name Stmts Miss Cover
|
||||
----------------------------------------------------------------------------------
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/__init__ 5 0 100%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/cal 234 7 97%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/caselessdict 55 5 91%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/compat 1 0 100%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/parser 189 6 97%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/parser_tools 20 0 100%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/prop 533 62 88%
|
||||
.tox/py27/lib/python2.7/site-packages/icalendar/tools 16 0 100%
|
||||
----------------------------------------------------------------------------------
|
||||
TOTAL 1053 80 92%
|
||||
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
3.6.1 (2014-01-13)
|
||||
------------------
|
||||
|
||||
- Open text files referenced by setup.py as utf-8, no matter what the locale
|
||||
settings are set to. Fixes #122.
|
||||
[sochotnicky]
|
||||
|
||||
- Add tox.ini to source tarball, which simplifies testing for in distributions.
|
||||
[sochotnicky]
|
||||
|
||||
|
||||
3.6 (2014-01-06)
|
||||
----------------
|
||||
|
||||
- Python3 (3.3+) + Python 2 (2.6+) support [geier]
|
||||
|
||||
- Made sure to_ical() always returns bytes [geier]
|
||||
|
||||
- Support adding lists to a component property, which value already was a list
|
||||
and remove the Component.set method, which was only used by the add method.
|
||||
[thet]
|
||||
|
||||
- Remove ability to add property parameters via a value's params attribute when
|
||||
adding via cal.add (that was only possible for custom value objects and makes
|
||||
up a strange API), but support a parameter attribute on cal.add's method
|
||||
signature to pass a dictionary with property parameter key/value pairs.
|
||||
Fixes #116.
|
||||
[thet]
|
||||
|
||||
- Backport some of Regebro's changes from his regebro-refactor branch.
|
||||
[thet]
|
||||
|
||||
- Raise explicit error on another malformed content line case.
|
||||
[hajdbo]
|
||||
|
||||
- Correctly parse datetime component property values with timezone information
|
||||
when parsed from ical strings.
|
||||
[untitaker]
|
||||
|
||||
|
||||
3.5 (2013-07-03)
|
||||
----------------
|
||||
|
||||
- Let to_unicode be more graceful for non-unicode strings, as like CMFPlone's
|
||||
safe_unicode does it.
|
||||
[thet]
|
||||
|
||||
|
||||
3.4 (2013-04-24)
|
||||
----------------
|
||||
|
||||
- Switch to unicode internally. This should fix all en/decoding errors.
|
||||
[thet]
|
||||
|
||||
- Support for non-ascii parameter values. Fixes #88.
|
||||
[warvariuc]
|
||||
|
||||
- Added functions to transform chars in string with '\\' + any of r'\,;:' chars
|
||||
into '%{:02X}' form to avoid splitting on chars escaped with '\\'.
|
||||
[warvariuc]
|
||||
|
||||
- Allow seconds in vUTCOffset properties. Fixes #55.
|
||||
[thet]
|
||||
|
||||
- Let ``Component.decode`` better handle vRecur and vDDDLists properties.
|
||||
Fixes #70.
|
||||
[thet]
|
||||
|
||||
- Don't let ``Component.add`` re-encode already encoded values. This simplifies
|
||||
the API, since there is no need explicitly pass ``encode=False``. Fixes #82.
|
||||
[thet]
|
||||
|
||||
- Rename tzinfo_from_dt to tzid_from_dt, which is what it does.
|
||||
[thet]
|
||||
|
||||
- More support for dateutil parsed tzinfo objects. Fixes #89.
|
||||
[leo-naeka]
|
||||
|
||||
- Remove python-dateutil version fix at all. Current python-dateutil has Py3
|
||||
and Py2 compatibility.
|
||||
[thet]
|
||||
|
||||
- Declare the required python-dateutil dependency in setup.py. Fixes #90.
|
||||
[kleink]
|
||||
|
||||
- Raise test coverage.
|
||||
[thet]
|
||||
|
||||
- Remove interfaces module, as it is unused.
|
||||
[thet]
|
||||
|
||||
- Remove ``test_doctests.py``, test suite already created properly in
|
||||
``test_icalendar.py``.
|
||||
[rnix]
|
||||
|
||||
- Transformed doctests into unittests, Test fixes and cleanup.
|
||||
[warvariuc]
|
||||
|
||||
|
||||
3.3 (2013-02-08)
|
||||
----------------
|
||||
|
||||
- Drop support for Python < 2.6.
|
||||
[thet]
|
||||
|
||||
- Allow vGeo to be instantiated with list and not only tuples of geo
|
||||
coordinates. Fixes #83.
|
||||
[thet]
|
||||
|
||||
- Don't force to pass a list to vDDDLists and allow setting individual RDATE
|
||||
and EXDATE values without having to wrap them in a list.
|
||||
[thet]
|
||||
|
||||
- Fix encoding function to allow setting RDATE and EXDATE values and not to
|
||||
have bypass encoding with an icalendar property.
|
||||
[thet]
|
||||
|
||||
- Allow setting of timezone for vDDDLists and support timezone properties for
|
||||
RDATE and EXDATE component properties.
|
||||
[thet]
|
||||
|
||||
- Move setting of TZID properties to vDDDTypes, where it belongs to.
|
||||
[thet]
|
||||
|
||||
- Use @staticmethod decorator instead of wrapper function.
|
||||
[warvariuc, thet]
|
||||
|
||||
- Extend quoting of parameter values to all of those characters: ",;: ’'".
|
||||
This fixes an outlook incompatibility with some characters. Fixes: #79,
|
||||
Fixes: #81.
|
||||
[warvariuc]
|
||||
|
||||
- Define VTIMETZONE subcomponents STANDARD and DAYLIGHT for RFC5545 compliance.
|
||||
[thet]
|
||||
|
||||
|
||||
3.2 (2012-11-27)
|
||||
----------------
|
||||
|
||||
- Documentation file layout restructuring.
|
||||
[thet]
|
||||
|
||||
- Fix time support. vTime events can be instantiated with a datetime.time
|
||||
object, and do not inherit from datetime.time itself.
|
||||
[rdunklau]
|
||||
|
||||
- Correctly handle tzinfo objects parsed with dateutil. Fixes #77.
|
||||
[warvariuc, thet]
|
||||
|
||||
- Text values are escaped correclty. Fixes #74.
|
||||
[warvariuc]
|
||||
|
||||
- Returned old folding algorithm, as the current implementation fails in some
|
||||
cases. Fixes #72, Fixes #73.
|
||||
[warvariuc]
|
||||
|
||||
- Supports to_ical() on date/time properties for dates prior to 1900.
|
||||
[cdevienne]
|
||||
|
||||
|
||||
3.1 (2012-09-05)
|
||||
----------------
|
||||
|
||||
- Make sure parameters to certain properties propagate to the ical output.
|
||||
[kanarip]
|
||||
|
||||
- Re-include doctests.
|
||||
[rnix]
|
||||
|
||||
- Ensure correct datatype at instance creation time in ``prop.vCalAddress``
|
||||
and ``prop.vText``.
|
||||
[rnix]
|
||||
|
||||
- Apply TZID parameter to datetimes parsed from RECURRENCE-ID
|
||||
[dbstovall]
|
||||
|
||||
- Localize datetimes for timezones to avoid DST transition errors.
|
||||
[dbstovall]
|
||||
|
||||
- Allow UTC-OFFSET property value data types in seconds, which follows RFC5545
|
||||
specification.
|
||||
[nikolaeff]
|
||||
|
||||
- Remove utctz and normalized_timezone methods to simplify the codebase. The
|
||||
methods were too tiny to be useful and just used at one place.
|
||||
[thet]
|
||||
|
||||
- When using Component.add() to add icalendar properties, force a value
|
||||
conversion to UTC for CREATED, DTSTART and LAST-MODIFIED. The RFC expects UTC
|
||||
for those properties.
|
||||
[thet]
|
||||
|
||||
- Removed last occurrences of old API (from_string).
|
||||
[Rembane]
|
||||
|
||||
- Add 'recursive' argument to property_items() to switch recursive listing.
|
||||
For example when parsing a text/calendar text including multiple components
|
||||
(e.g. a VCALENDAR with 5 VEVENTs), the previous situation required us to look
|
||||
over all properties in VEVENTs even if we just want the properties under the
|
||||
VCALENDAR component (VERSION, PRODID, CALSCALE, METHOD).
|
||||
[dmikurube]
|
||||
|
||||
- All unit tests fixed.
|
||||
[mikaelfrykholm]
|
||||
|
||||
|
||||
3.0.1b2 (2012-03-01)
|
||||
--------------------
|
||||
|
||||
- For all TZID parameters in DATE-TIME properties, use timezone identifiers
|
||||
(e.g. Europe/Vienna) instead of timezone names (e.g. CET), as required by
|
||||
RFC5545. Timezone names are used together with timezone identifiers in the
|
||||
Timezone components.
|
||||
[thet]
|
||||
|
||||
- Timezone parsing, issues and test fixes.
|
||||
[mikaelfrykholm, garbas, tgecho]
|
||||
|
||||
- Since we use pytz for timezones, also use UTC tzinfo object from the pytz
|
||||
library instead of own implementation.
|
||||
[thet]
|
||||
|
||||
|
||||
3.0.1b1 (2012-02-24)
|
||||
--------------------
|
||||
|
||||
- Update Release information.
|
||||
[thet]
|
||||
|
||||
|
||||
3.0
|
||||
---
|
||||
|
||||
- Add API for proper Timezone support. Allow creating ical DATE-TIME strings
|
||||
with timezone information from Python datetimes with pytz based timezone
|
||||
information and vice versa.
|
||||
[thet]
|
||||
|
||||
- Unify API to only use to_ical and from_ical and remove string casting as a
|
||||
requirement for Python 3 compatibility:
|
||||
New: to_ical.
|
||||
Old: ical, string, as_string and string casting via __str__ and str.
|
||||
New: from_ical.
|
||||
Old: from_string.
|
||||
[thet]
|
||||
|
||||
|
||||
2.2 (2011-08-24)
|
||||
----------------
|
||||
|
||||
- migration to https://github.com/collective/icalendar using svn2git preserving
|
||||
tags, branches and authors.
|
||||
[garbas]
|
||||
|
||||
- using tox for testing on python 2.4, 2.5, 2.6, 2.6.
|
||||
[garbas]
|
||||
|
||||
- fixed tests so they pass also under python 2.7.
|
||||
[garbas]
|
||||
|
||||
- running tests on https://jenkins.plone.org/job/icalendar (only 2.6 for now)
|
||||
with some other metrics (pylint, clonedigger, coverage).
|
||||
[garbas]
|
||||
|
||||
- review and merge changes from https://github.com/cozi/icalendar fork.
|
||||
[garbas]
|
||||
|
||||
- created sphinx documentation and started documenting development and goals.
|
||||
[garbas]
|
||||
|
||||
- hook out github repository to http://readthedocs.org service so sphinx
|
||||
documentation is generated on each commit (for master). Documentation can be
|
||||
visible on: http://readthedocs.org/docs/icalendar/en/latest/
|
||||
[garbas]
|
||||
|
||||
|
||||
2.1 (2009-12-14)
|
||||
----------------
|
||||
|
||||
- Fix deprecation warnings about ``object.__init__`` taking no parameters.
|
||||
|
||||
- Set the VALUE parameter correctly for date values.
|
||||
|
||||
- Long binary data would be base64 encoded with newlines, which made the
|
||||
iCalendar files incorrect. (This still needs testing).
|
||||
|
||||
- Correctly handle content lines which include newlines.
|
||||
|
||||
|
||||
2.0.1 (2008-07-11)
|
||||
------------------
|
||||
|
||||
- Made the tests run under Python 2.5+
|
||||
|
||||
- Renamed the UTC class to Utc, so it would not clash with the UTC object,
|
||||
since that rendered the UTC object unpicklable.
|
||||
|
||||
|
||||
2.0 (2008-07-11)
|
||||
----------------
|
||||
|
||||
- EXDATE and RDATE now returns a vDDDLists object, which contains a list
|
||||
of vDDDTypes objects. This is do that EXDATE and RDATE can contain
|
||||
lists of dates, as per RFC.
|
||||
|
||||
***Note!***: This change is incompatible with earlier behavior, so if you
|
||||
handle EXDATE and RDATE you will need to update your code.
|
||||
|
||||
- When createing a vDuration of -5 hours (which in itself is nonsensical),
|
||||
the ical output of that was -P1DT19H, which is correct, but ugly. Now
|
||||
it's '-PT5H', which is prettier.
|
||||
|
||||
|
||||
1.2 (2006-11-25)
|
||||
----------------
|
||||
|
||||
- Fixed a string index out of range error in the new folding code.
|
||||
|
||||
|
||||
1.1 (2006-11-23)
|
||||
----------------
|
||||
|
||||
- Fixed a bug in caselessdicts popitem. (thanks to Michael Smith
|
||||
<msmith@fluendo.com>)
|
||||
|
||||
- The RFC 2445 was a bit unclear on how to handle line folding when it
|
||||
happened to be in the middle of a UTF-8 character. This has been clarified
|
||||
in the following discussion:
|
||||
http://lists.osafoundation.org/pipermail/ietf-calsify/2006-August/001126.html
|
||||
And this is now implemented in iCalendar. It will not fold in the middle of
|
||||
a UTF-8 character, but may fold in the middle of a UTF-8 composing character
|
||||
sequence.
|
||||
|
||||
|
||||
1.0 (2006-08-03)
|
||||
----------------
|
||||
|
||||
- make get_inline and set_inline support non ascii codes.
|
||||
|
||||
- Added support for creating a python egg distribution.
|
||||
|
||||
|
||||
0.11 (2005-11-08)
|
||||
-----------------
|
||||
|
||||
- Changed component .from_string to use types_factory instead of hardcoding
|
||||
entries to 'inline'
|
||||
|
||||
- Changed UTC tzinfo to a singleton so the same one is used everywhere
|
||||
|
||||
- Made the parser more strict by using regular expressions for key name,
|
||||
param name and quoted/unquoted safe char as per the RFC
|
||||
|
||||
- Added some tests from the schooltool icalendar parser for better coverage
|
||||
|
||||
- Be more forgiving on the regex for folding lines
|
||||
|
||||
- Allow for multiple top-level components on .from_string
|
||||
|
||||
- Fix vWeekdays, wasn't accepting relative param (eg: -3SA vs -SA)
|
||||
|
||||
- vDDDTypes didn't accept negative period (eg: -P30M)
|
||||
|
||||
- 'N' is also acceptable as newline on content lines, per RFC
|
||||
|
||||
|
||||
0.10 (2005-04-28)
|
||||
-----------------
|
||||
|
||||
- moved code to codespeak.net subversion.
|
||||
|
||||
- reorganized package structure so that source code is under 'src' directory.
|
||||
Non-package files remain in distribution root.
|
||||
|
||||
- redid doc/.py files as doc/.txt, using more modern doctest. Before they
|
||||
were .py files with big docstrings.
|
||||
|
||||
- added test.py testrunner, and tests/test_icalendar.py that picks up all
|
||||
doctests in source code and doc directory, and runs them, when typing::
|
||||
|
||||
python2.3 test.py
|
||||
|
||||
- renamed iCalendar to lower case package name, lowercased, de-pluralized and
|
||||
shorted module names, which are mostly implementation detail.
|
||||
|
||||
- changed tests so they generate .ics files in a temp directory, not in the
|
||||
structure itself.
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
Copyright (c) 2012-2013, Plone Foundation
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Keywords: calendar calendaring ical icalendar event todo journal recurring
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
57
libs/icalendar-3.6.1/src/icalendar.egg-info/SOURCES.txt
Normal file
57
libs/icalendar-3.6.1/src/icalendar.egg-info/SOURCES.txt
Normal file
@ -0,0 +1,57 @@
|
||||
CHANGES.rst
|
||||
CONTRIBUTING.rst
|
||||
LICENSE.rst
|
||||
MANIFEST.in
|
||||
README.rst
|
||||
TODO.rst
|
||||
setup.cfg
|
||||
setup.py
|
||||
tox.ini
|
||||
docs/Makefile
|
||||
docs/about.rst
|
||||
docs/changelog.rst
|
||||
docs/conf.py
|
||||
docs/credits.rst
|
||||
docs/index.rst
|
||||
docs/install.rst
|
||||
docs/license.rst
|
||||
docs/usage.rst
|
||||
docs/_themes/icalendar/layout.html
|
||||
docs/_themes/icalendar/theme.conf
|
||||
docs/_themes/icalendar/static/icalendar.css
|
||||
src/icalendar/__init__.py
|
||||
src/icalendar/cal.py
|
||||
src/icalendar/caselessdict.py
|
||||
src/icalendar/compat.py
|
||||
src/icalendar/parser.py
|
||||
src/icalendar/parser_tools.py
|
||||
src/icalendar/prop.py
|
||||
src/icalendar/tools.py
|
||||
src/icalendar.egg-info/PKG-INFO
|
||||
src/icalendar.egg-info/SOURCES.txt
|
||||
src/icalendar.egg-info/dependency_links.txt
|
||||
src/icalendar.egg-info/not-zip-safe
|
||||
src/icalendar.egg-info/requires.txt
|
||||
src/icalendar.egg-info/top_level.txt
|
||||
src/icalendar/tests/__init__.py
|
||||
src/icalendar/tests/encoding.ics
|
||||
src/icalendar/tests/issue_112_missing_tzinfo_on_exdate.ics
|
||||
src/icalendar/tests/issue_114_invalid_line.ics
|
||||
src/icalendar/tests/issue_53_parsing_failure.ics
|
||||
src/icalendar/tests/multiple.ics
|
||||
src/icalendar/tests/recurrence.ics
|
||||
src/icalendar/tests/test_encoding.py
|
||||
src/icalendar/tests/test_fixed_issues.py
|
||||
src/icalendar/tests/test_icalendar.py
|
||||
src/icalendar/tests/test_multiple.py
|
||||
src/icalendar/tests/test_property_params.py
|
||||
src/icalendar/tests/test_recurrence.py
|
||||
src/icalendar/tests/test_time.py
|
||||
src/icalendar/tests/test_timezoned.py
|
||||
src/icalendar/tests/test_unit_cal.py
|
||||
src/icalendar/tests/test_unit_caselessdict.py
|
||||
src/icalendar/tests/test_unit_parser_tools.py
|
||||
src/icalendar/tests/test_unit_prop.py
|
||||
src/icalendar/tests/test_unit_tools.py
|
||||
src/icalendar/tests/time.ics
|
||||
src/icalendar/tests/timezoned.ics
|
@ -0,0 +1 @@
|
||||
|
1
libs/icalendar-3.6.1/src/icalendar.egg-info/not-zip-safe
Normal file
1
libs/icalendar-3.6.1/src/icalendar.egg-info/not-zip-safe
Normal file
@ -0,0 +1 @@
|
||||
|
5
libs/icalendar-3.6.1/src/icalendar.egg-info/requires.txt
Normal file
5
libs/icalendar-3.6.1/src/icalendar.egg-info/requires.txt
Normal file
@ -0,0 +1,5 @@
|
||||
setuptools
|
||||
python-dateutil
|
||||
pytz
|
||||
|
||||
[test]
|
@ -0,0 +1 @@
|
||||
icalendar
|
59
libs/icalendar-3.6.1/src/icalendar/__init__.py
Normal file
59
libs/icalendar-3.6.1/src/icalendar/__init__.py
Normal file
@ -0,0 +1,59 @@
|
||||
from icalendar.cal import (
|
||||
Calendar,
|
||||
Event,
|
||||
Todo,
|
||||
Journal,
|
||||
Timezone,
|
||||
TimezoneStandard,
|
||||
TimezoneDaylight,
|
||||
FreeBusy,
|
||||
Alarm,
|
||||
ComponentFactory,
|
||||
)
|
||||
# Property Data Value Types
|
||||
from icalendar.prop import (
|
||||
vBinary,
|
||||
vBoolean,
|
||||
vCalAddress,
|
||||
vDatetime,
|
||||
vDate,
|
||||
vDDDTypes,
|
||||
vDuration,
|
||||
vFloat,
|
||||
vInt,
|
||||
vPeriod,
|
||||
vWeekday,
|
||||
vFrequency,
|
||||
vRecur,
|
||||
vText,
|
||||
vTime,
|
||||
vUri,
|
||||
vGeo,
|
||||
vUTCOffset,
|
||||
TypesFactory,
|
||||
)
|
||||
# useful tzinfo subclasses
|
||||
from icalendar.prop import (
|
||||
FixedOffset,
|
||||
LocalTimezone,
|
||||
)
|
||||
# Parameters and helper methods for splitting and joining string with escaped
|
||||
# chars.
|
||||
from icalendar.parser import (
|
||||
Parameters,
|
||||
q_split,
|
||||
q_join,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
Calendar, Event, Todo, Journal,
|
||||
FreeBusy, Alarm, ComponentFactory,
|
||||
Timezone, TimezoneStandard, TimezoneDaylight,
|
||||
vBinary, vBoolean, vCalAddress, vDatetime, vDate,
|
||||
vDDDTypes, vDuration, vFloat, vInt, vPeriod,
|
||||
vWeekday, vFrequency, vRecur, vText, vTime, vUri,
|
||||
vGeo, vUTCOffset, TypesFactory,
|
||||
FixedOffset, LocalTimezone,
|
||||
Parameters, q_split, q_join,
|
||||
]
|
493
libs/icalendar-3.6.1/src/icalendar/cal.py
Normal file
493
libs/icalendar-3.6.1/src/icalendar/cal.py
Normal file
@ -0,0 +1,493 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Calendar is a dictionary like Python object that can render itself as VCAL
|
||||
files according to rfc2445.
|
||||
|
||||
These are the defined components.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from icalendar.caselessdict import CaselessDict
|
||||
from icalendar.parser import Contentline
|
||||
from icalendar.parser import Contentlines
|
||||
from icalendar.parser import Parameters
|
||||
from icalendar.parser import q_join
|
||||
from icalendar.parser import q_split
|
||||
from icalendar.parser_tools import DEFAULT_ENCODING
|
||||
from icalendar.parser_tools import data_encode
|
||||
from icalendar.prop import TypesFactory
|
||||
from icalendar.prop import vText, vDDDLists
|
||||
|
||||
import pytz
|
||||
|
||||
|
||||
######################################
|
||||
# The component factory
|
||||
|
||||
class ComponentFactory(CaselessDict):
|
||||
"""All components defined in rfc 2445 are registered in this factory class.
|
||||
To get a component you can use it like this.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Set keys to upper for initial dict.
|
||||
"""
|
||||
CaselessDict.__init__(self, *args, **kwargs)
|
||||
self['VEVENT'] = Event
|
||||
self['VTODO'] = Todo
|
||||
self['VJOURNAL'] = Journal
|
||||
self['VFREEBUSY'] = FreeBusy
|
||||
self['VTIMEZONE'] = Timezone
|
||||
self['STANDARD'] = TimezoneStandard
|
||||
self['DAYLIGHT'] = TimezoneDaylight
|
||||
self['VALARM'] = Alarm
|
||||
self['VCALENDAR'] = Calendar
|
||||
|
||||
|
||||
# These Properties have multiple property values inlined in one propertyline
|
||||
# seperated by comma. Use CaselessDict as simple caseless set.
|
||||
INLINE = CaselessDict(
|
||||
[(cat, 1) for cat in ('CATEGORIES', 'RESOURCES', 'FREEBUSY')]
|
||||
)
|
||||
|
||||
_marker = []
|
||||
|
||||
|
||||
class Component(CaselessDict):
|
||||
"""Component is the base object for calendar, Event and the other
|
||||
components defined in RFC 2445. normally you will not use this class
|
||||
directy, but rather one of the subclasses.
|
||||
"""
|
||||
|
||||
name = '' # must be defined in each component
|
||||
required = () # These properties are required
|
||||
singletons = () # These properties must only appear once
|
||||
multiple = () # may occur more than once
|
||||
exclusive = () # These properties are mutually exclusive
|
||||
inclusive = () # if any occurs the other(s) MUST occur
|
||||
# ('duration', 'repeat')
|
||||
ignore_exceptions = False # if True, and we cannot parse this
|
||||
# component, we will silently ignore
|
||||
# it, rather than let the exception
|
||||
# propagate upwards
|
||||
# not_compliant = [''] # List of non-compliant properties.
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Set keys to upper for initial dict.
|
||||
"""
|
||||
CaselessDict.__init__(self, *args, **kwargs)
|
||||
# set parameters here for properties that use non-default values
|
||||
self.subcomponents = [] # Components can be nested.
|
||||
self.is_broken = False # True if we ignored an exception while
|
||||
# parsing a property
|
||||
|
||||
#def is_compliant(self, name):
|
||||
# """Returns True is the given property name is compliant with the
|
||||
# icalendar implementation.
|
||||
#
|
||||
# If the parser is too strict it might prevent parsing erroneous but
|
||||
# otherwise compliant properties. So the parser is pretty lax, but it is
|
||||
# possible to test for non-complience by calling this method.
|
||||
# """
|
||||
# return name in not_compliant
|
||||
|
||||
#############################
|
||||
# handling of property values
|
||||
|
||||
def _encode(self, name, value, parameters=None, encode=1):
|
||||
"""Encode values to icalendar property values.
|
||||
|
||||
:param name: Name of the property.
|
||||
:type name: string
|
||||
|
||||
:param value: Value of the property. Either of a basic Python type of
|
||||
any of the icalendar's own property types.
|
||||
:type value: Python native type or icalendar property type.
|
||||
|
||||
:param parameters: Property parameter dictionary for the value. Only
|
||||
available, if encode is set to True.
|
||||
:type parameters: Dictionary
|
||||
|
||||
:param encode: True, if the value should be encoded to one of
|
||||
icalendar's own property types (Fallback is "vText")
|
||||
or False, if not.
|
||||
:type encode: Boolean
|
||||
|
||||
:returns: icalendar property value
|
||||
"""
|
||||
if not encode:
|
||||
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)
|
||||
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
|
||||
return obj
|
||||
|
||||
def add(self, name, value, parameters=None, encode=1):
|
||||
"""Add a property.
|
||||
|
||||
:param name: Name of the property.
|
||||
:type name: string
|
||||
|
||||
:param value: Value of the property. Either of a basic Python type of
|
||||
any of the icalendar's own property types.
|
||||
:type value: Python native type or icalendar property type.
|
||||
|
||||
:param parameters: Property parameter dictionary for the value. Only
|
||||
available, if encode is set to True.
|
||||
:type parameters: Dictionary
|
||||
|
||||
:param encode: True, if the value should be encoded to one of
|
||||
icalendar's own property types (Fallback is "vText")
|
||||
or False, if not.
|
||||
:type encode: Boolean
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
if isinstance(value, datetime) and\
|
||||
name.lower() in ('dtstamp', 'created', 'last-modified'):
|
||||
# RFC expects UTC for those... force value conversion.
|
||||
if getattr(value, 'tzinfo', False) and value.tzinfo is not None:
|
||||
value = value.astimezone(pytz.utc)
|
||||
else:
|
||||
# assume UTC for naive datetime instances
|
||||
value = pytz.utc.localize(value)
|
||||
|
||||
# encode value
|
||||
if encode and isinstance(value, list) \
|
||||
and name.lower() not in ['rdate', 'exdate']:
|
||||
# Individually convert each value to an ical type except rdate and
|
||||
# exdate, where lists of dates might be passed to vDDDLists.
|
||||
value = [self._encode(name, v, parameters, encode) for v in value]
|
||||
else:
|
||||
value = self._encode(name, value, parameters, encode)
|
||||
|
||||
# set value
|
||||
if name in self:
|
||||
# If property already exists, append it.
|
||||
#if name == 'attendee': import pdb; pdb.set_trace()
|
||||
oldval = self[name]
|
||||
if isinstance(oldval, list):
|
||||
if isinstance(value, list):
|
||||
value = oldval + value
|
||||
else:
|
||||
oldval.append(value)
|
||||
value = oldval
|
||||
else:
|
||||
value = [oldval, value]
|
||||
self[name] = value
|
||||
|
||||
def _decode(self, name, value):
|
||||
"""Internal for decoding property values.
|
||||
"""
|
||||
|
||||
# TODO: Currently the decoded method calls the icalendar.prop instances
|
||||
# from_ical. We probably want to decode properties into Python native
|
||||
# types here. But when parsing from an ical string with from_ical, we
|
||||
# want to encode the string into a real icalendar.prop property.
|
||||
if isinstance(value, vDDDLists):
|
||||
# TODO: Workaround unfinished decoding
|
||||
return value
|
||||
decoded = types_factory.from_ical(name, value)
|
||||
# TODO: remove when proper decoded is implemented in every prop.* class
|
||||
# Workaround to decode vText properly
|
||||
if isinstance(decoded, vText):
|
||||
decoded = decoded.encode(DEFAULT_ENCODING)
|
||||
return decoded
|
||||
|
||||
def decoded(self, name, default=_marker):
|
||||
"""Returns decoded value of property.
|
||||
"""
|
||||
# XXX: fail. what's this function supposed to do in the end?
|
||||
# -rnix
|
||||
|
||||
if name in self:
|
||||
value = self[name]
|
||||
if isinstance(value, list):
|
||||
return [self._decode(name, v) for v in value]
|
||||
return self._decode(name, value)
|
||||
else:
|
||||
if default is _marker:
|
||||
raise KeyError(name)
|
||||
else:
|
||||
return default
|
||||
|
||||
########################################################################
|
||||
# Inline values. A few properties have multiple values inlined in in one
|
||||
# property line. These methods are used for splitting and joining these.
|
||||
|
||||
def get_inline(self, name, decode=1):
|
||||
"""Returns a list of values (split on comma).
|
||||
"""
|
||||
vals = [v.strip('" ') for v in q_split(self[name])]
|
||||
if decode:
|
||||
return [self._decode(name, val) for val in vals]
|
||||
return vals
|
||||
|
||||
def set_inline(self, name, values, encode=1):
|
||||
"""Converts a list of values into comma seperated string and sets value
|
||||
to that.
|
||||
"""
|
||||
if encode:
|
||||
values = [self._encode(name, value, encode=1) for value in values]
|
||||
self[name] = types_factory['inline'](q_join(values))
|
||||
|
||||
#########################
|
||||
# Handling of components
|
||||
|
||||
def add_component(self, component):
|
||||
"""Add a subcomponent to this component.
|
||||
"""
|
||||
self.subcomponents.append(component)
|
||||
|
||||
def _walk(self, name):
|
||||
"""Walk to given component.
|
||||
"""
|
||||
result = []
|
||||
if name is None or self.name == name:
|
||||
result.append(self)
|
||||
for subcomponent in self.subcomponents:
|
||||
result += subcomponent._walk(name)
|
||||
return result
|
||||
|
||||
def walk(self, name=None):
|
||||
"""Recursively traverses component and subcomponents. Returns sequence
|
||||
of same. If name is passed, only components with name will be returned.
|
||||
"""
|
||||
if not name is None:
|
||||
name = name.upper()
|
||||
return self._walk(name)
|
||||
|
||||
#####################
|
||||
# Generation
|
||||
|
||||
def property_items(self, recursive=True):
|
||||
"""Returns properties in this component and subcomponents as:
|
||||
[(name, value), ...]
|
||||
"""
|
||||
vText = types_factory['text']
|
||||
properties = [('BEGIN', vText(self.name).to_ical())]
|
||||
property_names = self.sorted_keys()
|
||||
for name in property_names:
|
||||
values = self[name]
|
||||
if isinstance(values, list):
|
||||
# normally one property is one line
|
||||
for value in values:
|
||||
properties.append((name, value))
|
||||
else:
|
||||
properties.append((name, values))
|
||||
if recursive:
|
||||
# recursion is fun!
|
||||
for subcomponent in self.subcomponents:
|
||||
properties += subcomponent.property_items()
|
||||
properties.append(('END', vText(self.name).to_ical()))
|
||||
return properties
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, st, multiple=False):
|
||||
"""Populates the component recursively from a string.
|
||||
"""
|
||||
stack = [] # a stack of components
|
||||
comps = []
|
||||
for line in Contentlines.from_ical(st): # raw parsing
|
||||
if not line:
|
||||
continue
|
||||
name, params, vals = line.parts()
|
||||
uname = name.upper()
|
||||
# check for start of component
|
||||
if uname == 'BEGIN':
|
||||
# try and create one of the components defined in the spec,
|
||||
# otherwise get a general Components for robustness.
|
||||
c_name = vals.upper()
|
||||
c_class = component_factory.get(c_name, cls)
|
||||
component = c_class()
|
||||
if not getattr(component, 'name', ''): # undefined components
|
||||
component.name = c_name
|
||||
stack.append(component)
|
||||
# check for end of event
|
||||
elif uname == 'END':
|
||||
# we are done adding properties to this component
|
||||
# so pop it from the stack and add it to the new top.
|
||||
component = stack.pop()
|
||||
if not stack: # we are at the end
|
||||
comps.append(component)
|
||||
else:
|
||||
if not component.is_broken:
|
||||
stack[-1].add_component(component)
|
||||
# we are adding properties to the current top of the stack
|
||||
else:
|
||||
factory = types_factory.for_property(name)
|
||||
component = stack[-1]
|
||||
datetime_names = ('DTSTART', 'DTEND', 'RECURRENCE-ID', 'DUE',
|
||||
'FREEBUSY', 'RDATE', 'EXDATE')
|
||||
try:
|
||||
if name in datetime_names and 'TZID' in params:
|
||||
vals = factory(factory.from_ical(vals, params['TZID']))
|
||||
else:
|
||||
vals = factory(factory.from_ical(vals))
|
||||
except ValueError:
|
||||
if not component.ignore_exceptions:
|
||||
raise
|
||||
component.is_broken = True
|
||||
else:
|
||||
vals.params = params
|
||||
component.add(name, vals, encode=0)
|
||||
|
||||
if multiple:
|
||||
return comps
|
||||
if len(comps) > 1:
|
||||
raise ValueError('Found multiple components where '
|
||||
'only one is allowed: {st!r}'.format(**locals()))
|
||||
if len(comps) < 1:
|
||||
raise ValueError('Found no components where '
|
||||
'exactly one is required: '
|
||||
'{st!r}'.format(**locals()))
|
||||
return comps[0]
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.name, data_encode(self))
|
||||
|
||||
def content_line(self, name, value):
|
||||
"""Returns property as content line.
|
||||
"""
|
||||
params = getattr(value, 'params', Parameters())
|
||||
return Contentline.from_parts(name, params, value)
|
||||
|
||||
def content_lines(self):
|
||||
"""Converts the Component and subcomponents into content lines.
|
||||
"""
|
||||
contentlines = Contentlines()
|
||||
for name, value in self.property_items():
|
||||
cl = self.content_line(name, value)
|
||||
contentlines.append(cl)
|
||||
contentlines.append('') # remember the empty string in the end
|
||||
return contentlines
|
||||
|
||||
def to_ical(self):
|
||||
content_lines = self.content_lines()
|
||||
return content_lines.to_ical()
|
||||
|
||||
|
||||
#######################################
|
||||
# components defined in RFC 2445
|
||||
|
||||
class Event(Component):
|
||||
|
||||
name = 'VEVENT'
|
||||
|
||||
canonical_order = (
|
||||
'SUMMARY', 'DTSTART', 'DTEND', 'DURATION', 'DTSTAMP',
|
||||
'UID', 'RECURRENCE-ID', 'SEQUENCE',
|
||||
'RRULE' 'EXRULE', 'RDATE', 'EXDATE',
|
||||
)
|
||||
|
||||
required = ('UID',)
|
||||
singletons = (
|
||||
'CLASS', 'CREATED', 'DESCRIPTION', 'DTSTART', 'GEO', 'LAST-MODIFIED',
|
||||
'LOCATION', 'ORGANIZER', 'PRIORITY', 'DTSTAMP', 'SEQUENCE', 'STATUS',
|
||||
'SUMMARY', 'TRANSP', 'URL', 'RECURRENCE-ID', 'DTEND', 'DURATION',
|
||||
'DTSTART',
|
||||
)
|
||||
exclusive = ('DTEND', 'DURATION', )
|
||||
multiple = (
|
||||
'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
|
||||
'EXRULE', 'RSTATUS', 'RELATED', 'RESOURCES', 'RDATE', 'RRULE'
|
||||
)
|
||||
ignore_exceptions = True
|
||||
|
||||
|
||||
class Todo(Component):
|
||||
|
||||
name = 'VTODO'
|
||||
|
||||
required = ('UID',)
|
||||
singletons = (
|
||||
'CLASS', 'COMPLETED', 'CREATED', 'DESCRIPTION', 'DTSTAMP', 'DTSTART',
|
||||
'GEO', 'LAST-MODIFIED', 'LOCATION', 'ORGANIZER', 'PERCENT', 'PRIORITY',
|
||||
'RECURRENCE-ID', 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL', 'DUE',
|
||||
'DURATION',
|
||||
)
|
||||
exclusive = ('DUE', 'DURATION',)
|
||||
multiple = (
|
||||
'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
|
||||
'EXRULE', 'RSTATUS', 'RELATED', 'RESOURCES', 'RDATE', 'RRULE'
|
||||
)
|
||||
|
||||
|
||||
class Journal(Component):
|
||||
|
||||
name = 'VJOURNAL'
|
||||
|
||||
required = ('UID',)
|
||||
singletons = (
|
||||
'CLASS', 'CREATED', 'DESCRIPTION', 'DTSTART', 'DTSTAMP',
|
||||
'LAST-MODIFIED', 'ORGANIZER', 'RECURRENCE-ID', 'SEQUENCE', 'STATUS',
|
||||
'SUMMARY', 'UID', 'URL',
|
||||
)
|
||||
multiple = (
|
||||
'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
|
||||
'EXRULE', 'RELATED', 'RDATE', 'RRULE', 'RSTATUS',
|
||||
)
|
||||
|
||||
|
||||
class FreeBusy(Component):
|
||||
|
||||
name = 'VFREEBUSY'
|
||||
|
||||
required = ('UID',)
|
||||
singletons = (
|
||||
'CONTACT', 'DTSTART', 'DTEND', 'DURATION', 'DTSTAMP', 'ORGANIZER',
|
||||
'UID', 'URL',
|
||||
)
|
||||
multiple = ('ATTENDEE', 'COMMENT', 'FREEBUSY', 'RSTATUS',)
|
||||
|
||||
|
||||
class Timezone(Component):
|
||||
name = 'VTIMEZONE'
|
||||
canonical_order = ('TZID', 'STANDARD', 'DAYLIGHT',)
|
||||
required = ('TZID', 'STANDARD', 'DAYLIGHT',)
|
||||
singletons = ('TZID', 'LAST-MODIFIED', 'TZURL',)
|
||||
|
||||
|
||||
class TimezoneStandard(Component):
|
||||
name = 'STANDARD'
|
||||
required = ('DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM')
|
||||
singletons = ('DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM', 'RRULE')
|
||||
multiple = ('COMMENT', 'RDATE', 'TZNAME')
|
||||
|
||||
|
||||
class TimezoneDaylight(Component):
|
||||
name = 'DAYLIGHT'
|
||||
required = ('DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM')
|
||||
singletons = ('DTSTART', 'TZOFFSETTO', 'TZOFFSETFROM', 'RRULE')
|
||||
multiple = ('COMMENT', 'RDATE', 'TZNAME')
|
||||
|
||||
|
||||
class Alarm(Component):
|
||||
|
||||
name = 'VALARM'
|
||||
# not quite sure about these ...
|
||||
required = ('ACTION', 'TRIGGER',)
|
||||
singletons = ('ATTACH', 'ACTION', 'TRIGGER', 'DURATION', 'REPEAT',)
|
||||
inclusive = (('DURATION', 'REPEAT',),)
|
||||
|
||||
|
||||
class Calendar(Component):
|
||||
"""This is the base object for an iCalendar file.
|
||||
"""
|
||||
name = 'VCALENDAR'
|
||||
canonical_order = ('VERSION', 'PRODID', 'CALSCALE', 'METHOD',)
|
||||
required = ('prodid', 'version', )
|
||||
singletons = ('prodid', 'version', )
|
||||
multiple = ('calscale', 'method', )
|
||||
|
||||
# These are read only singleton, so one instance is enough for the module
|
||||
types_factory = TypesFactory()
|
||||
component_factory = ComponentFactory()
|
98
libs/icalendar-3.6.1/src/icalendar/caselessdict.py
Normal file
98
libs/icalendar-3.6.1/src/icalendar/caselessdict.py
Normal file
@ -0,0 +1,98 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from icalendar.parser_tools import to_unicode
|
||||
from icalendar.parser_tools import data_encode
|
||||
|
||||
|
||||
def canonsort_keys(keys, canonical_order=None):
|
||||
"""Sorts leading keys according to canonical_order. Keys not specified in
|
||||
canonical_order will appear alphabetically at the end.
|
||||
"""
|
||||
canonical_map = dict((k, i) for i, k in enumerate(canonical_order or []))
|
||||
head = [k for k in keys if k in canonical_map]
|
||||
tail = [k for k in keys if k not in canonical_map]
|
||||
return sorted(head, key=lambda k: canonical_map[k]) + sorted(tail)
|
||||
|
||||
|
||||
def canonsort_items(dict1, canonical_order=None):
|
||||
"""Returns a list of items from dict1, sorted by canonical_order.
|
||||
"""
|
||||
return [(k, dict1[k]) for \
|
||||
k in canonsort_keys(dict1.keys(), canonical_order)]
|
||||
|
||||
|
||||
class CaselessDict(dict):
|
||||
"""A dictionary that isn't case sensitive, and only uses strings as keys.
|
||||
Values retain their case.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Set keys to upper for initial dict.
|
||||
"""
|
||||
dict.__init__(self, *args, **kwargs)
|
||||
for key, value in self.items():
|
||||
key_upper = to_unicode(key).upper()
|
||||
if key != key_upper:
|
||||
dict.__delitem__(self, key)
|
||||
self[key_upper] = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
key = to_unicode(key)
|
||||
return dict.__getitem__(self, key.upper())
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
key = to_unicode(key)
|
||||
dict.__setitem__(self, key.upper(), value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
key = to_unicode(key)
|
||||
dict.__delitem__(self, key.upper())
|
||||
|
||||
def __contains__(self, key):
|
||||
key = to_unicode(key)
|
||||
return dict.__contains__(self, key.upper())
|
||||
|
||||
def get(self, key, default=None):
|
||||
key = to_unicode(key)
|
||||
return dict.get(self, key.upper(), default)
|
||||
|
||||
def setdefault(self, key, value=None):
|
||||
key = to_unicode(key)
|
||||
return dict.setdefault(self, key.upper(), value)
|
||||
|
||||
def pop(self, key, default=None):
|
||||
key = to_unicode(key)
|
||||
return dict.pop(self, key.upper(), default)
|
||||
|
||||
def popitem(self):
|
||||
return dict.popitem(self)
|
||||
|
||||
def has_key(self, key):
|
||||
key = to_unicode(key)
|
||||
return dict.__contains__(self, key.upper())
|
||||
|
||||
def update(self, indict):
|
||||
# Multiple keys where key1.upper() == key2.upper() will be lost.
|
||||
for key, value in indict.items(): # TODO optimize in python 2
|
||||
self[key] = value
|
||||
|
||||
def copy(self):
|
||||
return CaselessDict(dict.copy(self))
|
||||
|
||||
def __repr__(self):
|
||||
return 'CaselessDict(%s)' % data_encode(self)
|
||||
|
||||
# A list of keys that must appear first in sorted_keys and sorted_items;
|
||||
# must be uppercase.
|
||||
canonical_order = None
|
||||
|
||||
def sorted_keys(self):
|
||||
"""Sorts keys according to the canonical_order for the derived class.
|
||||
Keys not specified in canonical_order will appear at the end.
|
||||
"""
|
||||
return canonsort_keys(self.keys(), self.canonical_order)
|
||||
|
||||
def sorted_items(self):
|
||||
"""Sorts items according to the canonical_order for the derived class.
|
||||
Items not specified in canonical_order will appear at the end.
|
||||
"""
|
||||
return canonsort_items(self, self.canonical_order)
|
11
libs/icalendar-3.6.1/src/icalendar/compat.py
Normal file
11
libs/icalendar-3.6.1/src/icalendar/compat.py
Normal file
@ -0,0 +1,11 @@
|
||||
import sys
|
||||
|
||||
|
||||
if sys.version_info[0] == 2: # pragma: no cover
|
||||
unicode_type = unicode
|
||||
bytes_type = str
|
||||
iteritems = lambda d, *args, **kwargs: iter(d.iteritems(*args, **kwargs))
|
||||
else: # pragma: no cover
|
||||
unicode_type = str
|
||||
bytes_type = bytes
|
||||
iteritems = lambda d, *args, **kwargs: iter(d.items(*args, **kwargs))
|
371
libs/icalendar-3.6.1/src/icalendar/parser.py
Normal file
371
libs/icalendar-3.6.1/src/icalendar/parser.py
Normal file
@ -0,0 +1,371 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""This module parses and generates contentlines as defined in RFC 2445
|
||||
(iCalendar), but will probably work for other MIME types with similar syntax.
|
||||
Eg. RFC 2426 (vCard)
|
||||
|
||||
It is stupid in the sense that it treats the content purely as strings. No type
|
||||
conversion is attempted.
|
||||
"""
|
||||
from icalendar import compat
|
||||
from icalendar.caselessdict import CaselessDict
|
||||
from icalendar.parser_tools import DEFAULT_ENCODING
|
||||
from icalendar.parser_tools import SEQUENCE_TYPES
|
||||
from icalendar.parser_tools import data_encode
|
||||
from icalendar.parser_tools import to_unicode
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def escape_char(text):
|
||||
"""Format value according to iCalendar TEXT escaping rules.
|
||||
"""
|
||||
assert isinstance(text, (compat.unicode_type, compat.bytes_type))
|
||||
# NOTE: ORDER MATTERS!
|
||||
return text.replace(r'\N', '\n')\
|
||||
.replace('\\', '\\\\')\
|
||||
.replace(';', r'\;')\
|
||||
.replace(',', r'\,')\
|
||||
.replace('\r\n', r'\n')\
|
||||
.replace('\n', r'\n')
|
||||
|
||||
|
||||
def unescape_char(text):
|
||||
assert isinstance(text, (compat.unicode_type, compat.bytes_type))
|
||||
# NOTE: ORDER MATTERS!
|
||||
if isinstance(text, compat.unicode_type):
|
||||
return text.replace(u'\\N', u'\\n')\
|
||||
.replace(u'\r\n', u'\n')\
|
||||
.replace(u'\\n', u'\n')\
|
||||
.replace(u'\\,', u',')\
|
||||
.replace(u'\\;', u';')\
|
||||
.replace(u'\\\\', u'\\')
|
||||
elif isinstance(text, compat.bytes_type):
|
||||
return text.replace(b'\N', b'\n')\
|
||||
.replace(b'\r\n', b'\n')\
|
||||
.replace(b'\n', b'\n')\
|
||||
.replace(b'\,', b',')\
|
||||
.replace(b'\;', b';')\
|
||||
.replace(b'\\\\', b'\\')
|
||||
|
||||
|
||||
def tzid_from_dt(dt):
|
||||
tzid = None
|
||||
if hasattr(dt.tzinfo, 'zone'):
|
||||
tzid = dt.tzinfo.zone # pytz implementation
|
||||
elif hasattr(dt.tzinfo, 'tzname'):
|
||||
try:
|
||||
tzid = dt.tzinfo.tzname(dt) # dateutil implementation
|
||||
except AttributeError:
|
||||
# No tzid available
|
||||
pass
|
||||
return tzid
|
||||
|
||||
|
||||
def foldline(line, limit=75, fold_sep=u'\r\n '):
|
||||
"""Make a string folded as defined in RFC5545
|
||||
Lines of text SHOULD NOT be longer than 75 octets, excluding the line
|
||||
break. Long content lines SHOULD be split into a multiple line
|
||||
representations using a line "folding" technique. That is, a long
|
||||
line can be split between any two characters by inserting a CRLF
|
||||
immediately followed by a single linear white-space character (i.e.,
|
||||
SPACE or HTAB).
|
||||
"""
|
||||
assert isinstance(line, compat.unicode_type)
|
||||
assert u'\n' not in line
|
||||
|
||||
ret_line = u''
|
||||
byte_count = 0
|
||||
for char in line:
|
||||
char_byte_len = len(char.encode(DEFAULT_ENCODING))
|
||||
byte_count += char_byte_len
|
||||
if byte_count >= limit:
|
||||
ret_line += fold_sep
|
||||
byte_count = char_byte_len
|
||||
ret_line += char
|
||||
|
||||
return ret_line
|
||||
|
||||
|
||||
#################################################################
|
||||
# Property parameter stuff
|
||||
|
||||
def param_value(value):
|
||||
"""Returns a parameter value.
|
||||
"""
|
||||
if isinstance(value, SEQUENCE_TYPES):
|
||||
return q_join(value)
|
||||
return dquote(value)
|
||||
|
||||
|
||||
# Could be improved
|
||||
NAME = re.compile('[\w-]+')
|
||||
UNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F",:;]')
|
||||
QUNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F"]')
|
||||
FOLD = re.compile(b'(\r?\n)+[ \t]')
|
||||
uFOLD = re.compile(u'(\r?\n)+[ \t]')
|
||||
NEWLINE = re.compile(r'\r?\n')
|
||||
|
||||
|
||||
def validate_token(name):
|
||||
match = NAME.findall(name)
|
||||
if len(match) == 1 and name == match[0]:
|
||||
return
|
||||
raise ValueError(name)
|
||||
|
||||
|
||||
def validate_param_value(value, quoted=True):
|
||||
validator = QUNSAFE_CHAR if quoted else UNSAFE_CHAR
|
||||
if validator.findall(value):
|
||||
raise ValueError(value)
|
||||
|
||||
|
||||
# chars presence of which in parameter value will be cause the value
|
||||
# to be enclosed in double-quotes
|
||||
QUOTABLE = re.compile("[,;: ’']")
|
||||
|
||||
|
||||
def dquote(val):
|
||||
"""Enclose parameter values containing [,;:] in double quotes.
|
||||
"""
|
||||
# a double-quote character is forbidden to appear in a parameter value
|
||||
# so replace it with a single-quote character
|
||||
val = val.replace('"', "'")
|
||||
if QUOTABLE.search(val):
|
||||
return '"%s"' % val
|
||||
return val
|
||||
|
||||
|
||||
# parsing helper
|
||||
def q_split(st, sep=','):
|
||||
"""Splits a string on char, taking double (q)uotes into considderation.
|
||||
"""
|
||||
result = []
|
||||
cursor = 0
|
||||
length = len(st)
|
||||
inquote = 0
|
||||
for i in range(length):
|
||||
ch = st[i]
|
||||
if ch == '"':
|
||||
inquote = not inquote
|
||||
if not inquote and ch == sep:
|
||||
result.append(st[cursor:i])
|
||||
cursor = i + 1
|
||||
if i + 1 == length:
|
||||
result.append(st[cursor:])
|
||||
return result
|
||||
|
||||
|
||||
def q_join(lst, sep=','):
|
||||
"""Joins a list on sep, quoting strings with QUOTABLE chars.
|
||||
"""
|
||||
return sep.join(dquote(itm) for itm in lst)
|
||||
|
||||
|
||||
class Parameters(CaselessDict):
|
||||
"""Parser and generator of Property parameter strings. It knows nothing of
|
||||
datatypes. Its main concern is textual structure.
|
||||
"""
|
||||
|
||||
def params(self):
|
||||
"""In rfc2445 keys are called parameters, so this is to be consitent
|
||||
with the naming conventions.
|
||||
"""
|
||||
return self.keys()
|
||||
|
||||
# TODO?
|
||||
# Later, when I get more time... need to finish this off now. The last major
|
||||
# thing missing.
|
||||
# def _encode(self, name, value, cond=1):
|
||||
# # internal, for conditional convertion of values.
|
||||
# if cond:
|
||||
# klass = types_factory.for_property(name)
|
||||
# return klass(value)
|
||||
# return value
|
||||
#
|
||||
# def add(self, name, value, encode=0):
|
||||
# "Add a parameter value and optionally encode it."
|
||||
# if encode:
|
||||
# value = self._encode(name, value, encode)
|
||||
# self[name] = value
|
||||
#
|
||||
# def decoded(self, name):
|
||||
# "returns a decoded value, or list of same"
|
||||
|
||||
def __repr__(self):
|
||||
return 'Parameters(%s)' % data_encode(self)
|
||||
|
||||
def to_ical(self):
|
||||
result = []
|
||||
items = self.items()
|
||||
for key, value in sorted(items):
|
||||
value = param_value(value)
|
||||
if isinstance(value, compat.unicode_type):
|
||||
value = value.encode(DEFAULT_ENCODING)
|
||||
# CaselessDict keys are always unicode
|
||||
key = key.upper().encode(DEFAULT_ENCODING)
|
||||
result.append(key + b'=' + value)
|
||||
return b';'.join(result)
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, st, strict=False):
|
||||
"""Parses the parameter format from ical text format."""
|
||||
|
||||
# parse into strings
|
||||
result = cls()
|
||||
for param in q_split(st, ';'):
|
||||
try:
|
||||
key, val = q_split(param, '=')
|
||||
validate_token(key)
|
||||
# Property parameter values that are not in quoted
|
||||
# strings are case insensitive.
|
||||
vals = []
|
||||
for v in q_split(val, ','):
|
||||
if v.startswith('"') and v.endswith('"'):
|
||||
v = v.strip('"')
|
||||
validate_param_value(v, quoted=True)
|
||||
vals.append(v)
|
||||
else:
|
||||
validate_param_value(v, quoted=False)
|
||||
if strict:
|
||||
vals.append(v.upper())
|
||||
else:
|
||||
vals.append(v)
|
||||
if not vals:
|
||||
result[key] = val
|
||||
else:
|
||||
if len(vals) == 1:
|
||||
result[key] = vals[0]
|
||||
else:
|
||||
result[key] = vals
|
||||
except ValueError as exc:
|
||||
raise ValueError('%r is not a valid parameter string: %s'
|
||||
% (param, exc))
|
||||
return result
|
||||
|
||||
|
||||
def escape_string(val):
|
||||
# '%{:02X}'.format(i)
|
||||
return val.replace(r'\,', '%2C').replace(r'\:', '%3A')\
|
||||
.replace(r'\;', '%3B').replace(r'\\', '%5C')
|
||||
|
||||
|
||||
def unsescape_string(val):
|
||||
return val.replace('%2C', ',').replace('%3A', ':')\
|
||||
.replace('%3B', ';').replace('%5C', '\\')
|
||||
|
||||
|
||||
#########################################
|
||||
# parsing and generation of content lines
|
||||
|
||||
class Contentline(compat.unicode_type):
|
||||
"""A content line is basically a string that can be folded and parsed into
|
||||
parts.
|
||||
"""
|
||||
def __new__(cls, value, strict=False, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
assert u'\n' not in value, ('Content line can not contain unescaped '
|
||||
'new line characters.')
|
||||
self = super(Contentline, cls).__new__(cls, value)
|
||||
self.strict = strict
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_parts(cls, name, params, values):
|
||||
"""Turn a parts into a content line.
|
||||
"""
|
||||
assert isinstance(params, Parameters)
|
||||
if hasattr(values, 'to_ical'):
|
||||
values = values.to_ical()
|
||||
else:
|
||||
values = vText(values).to_ical()
|
||||
# elif isinstance(values, basestring):
|
||||
# values = escape_char(values)
|
||||
|
||||
# TODO: after unicode only, remove this
|
||||
# Convert back to unicode, after to_ical encoded it.
|
||||
name = to_unicode(name)
|
||||
values = to_unicode(values)
|
||||
if params:
|
||||
params = to_unicode(params.to_ical())
|
||||
return cls(u'%s;%s:%s' % (name, params, values))
|
||||
return cls(u'%s:%s' % (name, values))
|
||||
|
||||
def parts(self):
|
||||
"""Split the content line up into (name, parameters, values) parts.
|
||||
"""
|
||||
try:
|
||||
st = escape_string(self)
|
||||
name_split = None
|
||||
value_split = None
|
||||
in_quotes = False
|
||||
for i, ch in enumerate(st):
|
||||
if not in_quotes:
|
||||
if ch in ':;' and not name_split:
|
||||
name_split = i
|
||||
if ch == ':' and not value_split:
|
||||
value_split = i
|
||||
if ch == '"':
|
||||
in_quotes = not in_quotes
|
||||
name = unsescape_string(st[:name_split])
|
||||
if not name:
|
||||
raise ValueError('Key name is required')
|
||||
validate_token(name)
|
||||
if not name_split or name_split + 1 == value_split:
|
||||
raise ValueError('Invalid content line')
|
||||
params = Parameters.from_ical(st[name_split + 1: value_split],
|
||||
strict=self.strict)
|
||||
params = Parameters(
|
||||
(unsescape_string(key), unsescape_string(value))
|
||||
for key, value in compat.iteritems(params)
|
||||
)
|
||||
values = unsescape_string(st[value_split + 1:])
|
||||
return (name, params, values)
|
||||
except ValueError as exc:
|
||||
raise ValueError(
|
||||
u"Content line could not be parsed into parts: %r: %s"
|
||||
% (self, exc)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical, strict=False):
|
||||
"""Unfold the content lines in an iCalendar into long content lines.
|
||||
"""
|
||||
ical = to_unicode(ical)
|
||||
# a fold is carriage return followed by either a space or a tab
|
||||
return cls(uFOLD.sub('', ical), strict=strict)
|
||||
|
||||
def to_ical(self):
|
||||
"""Long content lines are folded so they are less than 75 characters
|
||||
wide.
|
||||
"""
|
||||
return foldline(self).encode(DEFAULT_ENCODING)
|
||||
|
||||
|
||||
class Contentlines(list):
|
||||
"""I assume that iCalendar files generally are a few kilobytes in size.
|
||||
Then this should be efficient. for Huge files, an iterator should probably
|
||||
be used instead.
|
||||
"""
|
||||
def to_ical(self):
|
||||
"""Simply join self.
|
||||
"""
|
||||
return b'\r\n'.join(line.to_ical() for line in self if line) + b'\r\n'
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, st):
|
||||
"""Parses a string into content lines.
|
||||
"""
|
||||
st = to_unicode(st)
|
||||
try:
|
||||
# a fold is carriage return followed by either a space or a tab
|
||||
unfolded = uFOLD.sub('', st)
|
||||
lines = cls(Contentline(line) for
|
||||
line in unfolded.splitlines() if line)
|
||||
lines.append('') # '\r\n' at the end of every content line
|
||||
return lines
|
||||
except:
|
||||
raise ValueError('Expected StringType with content lines')
|
||||
|
||||
|
||||
# XXX: what kind of hack is this? import depends to be at end
|
||||
from icalendar.prop import vText
|
33
libs/icalendar-3.6.1/src/icalendar/parser_tools.py
Normal file
33
libs/icalendar-3.6.1/src/icalendar/parser_tools.py
Normal file
@ -0,0 +1,33 @@
|
||||
from icalendar import compat
|
||||
|
||||
|
||||
SEQUENCE_TYPES = (list, tuple)
|
||||
DEFAULT_ENCODING = 'utf-8'
|
||||
|
||||
|
||||
def to_unicode(value, encoding='utf-8'):
|
||||
"""Converts a value to unicode, even if it is already a unicode string.
|
||||
"""
|
||||
if isinstance(value, compat.unicode_type):
|
||||
return value
|
||||
elif isinstance(value, compat.bytes_type):
|
||||
try:
|
||||
value = value.decode(encoding)
|
||||
except UnicodeDecodeError:
|
||||
value = value.decode('utf-8', 'replace')
|
||||
return value
|
||||
|
||||
|
||||
def data_encode(data, encoding=DEFAULT_ENCODING):
|
||||
"""Encode all datastructures to the given encoding.
|
||||
Currently unicode strings, dicts and lists are supported.
|
||||
"""
|
||||
# http://stackoverflow.com/questions/1254454/fastest-way-to-convert-a-dicts-keys-values-from-unicode-to-str
|
||||
if isinstance(data, compat.unicode_type):
|
||||
return data.encode(encoding)
|
||||
elif isinstance(data, dict):
|
||||
return dict(map(data_encode, compat.iteritems(data)))
|
||||
elif isinstance(data, list) or isinstance(data, tuple):
|
||||
return list(map(data_encode, data))
|
||||
else:
|
||||
return data
|
993
libs/icalendar-3.6.1/src/icalendar/prop.py
Normal file
993
libs/icalendar-3.6.1/src/icalendar/prop.py
Normal file
@ -0,0 +1,993 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""This module contains the parser/generators (or coders/encoders if you
|
||||
prefer) for the classes/datatypes that are used in iCalendar:
|
||||
|
||||
###########################################################################
|
||||
# This module defines these property value data types and property parameters
|
||||
|
||||
4.2 Defined property parameters are:
|
||||
|
||||
ALTREP, CN, CUTYPE, DELEGATED-FROM, DELEGATED-TO, DIR, ENCODING, FMTTYPE,
|
||||
FBTYPE, LANGUAGE, MEMBER, PARTSTAT, RANGE, RELATED, RELTYPE, ROLE, RSVP,
|
||||
SENT-BY, TZID, VALUE
|
||||
|
||||
4.3 Defined value data types are:
|
||||
|
||||
BINARY, BOOLEAN, CAL-ADDRESS, DATE, DATE-TIME, DURATION, FLOAT, INTEGER,
|
||||
PERIOD, RECUR, TEXT, TIME, URI, UTC-OFFSET
|
||||
|
||||
###########################################################################
|
||||
|
||||
iCalendar properties has values. The values are strongly typed. This module
|
||||
defines these types, calling val.to_ical() on them, Will render them as defined
|
||||
in rfc2445.
|
||||
|
||||
If you pass any of these classes a Python primitive, you will have an object
|
||||
that can render itself as iCalendar formatted date.
|
||||
|
||||
Property Value Data Types starts with a 'v'. they all have an to_ical() and
|
||||
from_ical() method. The to_ical() method generates a text string in the
|
||||
iCalendar format. The from_ical() method can parse this format and return a
|
||||
primitive Python datatype. So it should allways be true that:
|
||||
|
||||
x == vDataType.from_ical(VDataType(x).to_ical())
|
||||
|
||||
These types are mainly used for parsing and file generation. But you can set
|
||||
them directly.
|
||||
"""
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
from datetime import time
|
||||
from datetime import timedelta
|
||||
from datetime import tzinfo
|
||||
from dateutil.tz import tzutc
|
||||
from icalendar import compat
|
||||
from icalendar.caselessdict import CaselessDict
|
||||
from icalendar.parser import Parameters
|
||||
from icalendar.parser import escape_char
|
||||
from icalendar.parser import tzid_from_dt
|
||||
from icalendar.parser import unescape_char
|
||||
from icalendar.parser_tools import DEFAULT_ENCODING
|
||||
from icalendar.parser_tools import SEQUENCE_TYPES
|
||||
from icalendar.parser_tools import to_unicode
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import pytz
|
||||
import re
|
||||
import time as _time
|
||||
|
||||
|
||||
DATE_PART = r'(\d+)D'
|
||||
TIME_PART = r'T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?'
|
||||
DATETIME_PART = '(?:%s)?(?:%s)?' % (DATE_PART, TIME_PART)
|
||||
WEEKS_PART = r'(\d+)W'
|
||||
DURATION_REGEX = re.compile(r'([-+]?)P(?:%s|%s)$'
|
||||
% (WEEKS_PART, DATETIME_PART))
|
||||
WEEKDAY_RULE = re.compile('(?P<signal>[+-]?)(?P<relative>[\d]?)'
|
||||
'(?P<weekday>[\w]{2})$')
|
||||
|
||||
|
||||
####################################################
|
||||
# handy tzinfo classes you can use.
|
||||
#
|
||||
|
||||
ZERO = timedelta(0)
|
||||
HOUR = timedelta(hours=1)
|
||||
STDOFFSET = timedelta(seconds=-_time.timezone)
|
||||
if _time.daylight:
|
||||
DSTOFFSET = timedelta(seconds=-_time.altzone)
|
||||
else:
|
||||
DSTOFFSET = STDOFFSET
|
||||
DSTDIFF = DSTOFFSET - STDOFFSET
|
||||
|
||||
|
||||
class FixedOffset(tzinfo):
|
||||
"""Fixed offset in minutes east from UTC.
|
||||
"""
|
||||
def __init__(self, offset, name):
|
||||
self.__offset = timedelta(minutes=offset)
|
||||
self.__name = name
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self.__offset
|
||||
|
||||
def tzname(self, dt):
|
||||
return self.__name
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
|
||||
class LocalTimezone(tzinfo):
|
||||
"""Timezone of the machine where the code is running.
|
||||
"""
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return DSTOFFSET
|
||||
else:
|
||||
return STDOFFSET
|
||||
|
||||
def dst(self, dt):
|
||||
if self._isdst(dt):
|
||||
return DSTDIFF
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return _time.tzname[self._isdst(dt)]
|
||||
|
||||
def _isdst(self, dt):
|
||||
tt = (dt.year, dt.month, dt.day,
|
||||
dt.hour, dt.minute, dt.second,
|
||||
dt.weekday(), 0, -1)
|
||||
stamp = _time.mktime(tt)
|
||||
tt = _time.localtime(stamp)
|
||||
return tt.tm_isdst > 0
|
||||
|
||||
|
||||
class vBinary(object):
|
||||
"""Binary property values are base 64 encoded.
|
||||
"""
|
||||
|
||||
def __init__(self, obj):
|
||||
self.obj = to_unicode(obj)
|
||||
self.params = Parameters(encoding='BASE64', value="BINARY")
|
||||
|
||||
def __repr__(self):
|
||||
return "vBinary('%s')" % self.to_ical()
|
||||
|
||||
def to_ical(self):
|
||||
return binascii.b2a_base64(self.obj.encode('utf-8'))[:-1]
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical):
|
||||
try:
|
||||
return base64.b64decode(ical)
|
||||
except UnicodeError:
|
||||
raise ValueError('Not valid base 64 encoding.')
|
||||
|
||||
|
||||
class vBoolean(int):
|
||||
"""Returns specific string according to state.
|
||||
"""
|
||||
BOOL_MAP = CaselessDict(true=True, false=False)
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
self = super(vBoolean, cls).__new__(cls, *args, **kwargs)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
if self:
|
||||
return b'TRUE'
|
||||
return b'FALSE'
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
try:
|
||||
return cls.BOOL_MAP[ical]
|
||||
except:
|
||||
raise ValueError("Expected 'TRUE' or 'FALSE'. Got %s" % ical)
|
||||
|
||||
|
||||
class vCalAddress(compat.unicode_type):
|
||||
"""This just returns an unquoted string.
|
||||
"""
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
self = super(vCalAddress, cls).__new__(cls, value)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "vCalAddress('%s')" % self.to_ical()
|
||||
|
||||
def to_ical(self):
|
||||
return self.encode(DEFAULT_ENCODING)
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
return cls(ical)
|
||||
|
||||
|
||||
class vFloat(float):
|
||||
"""Just a float.
|
||||
"""
|
||||
def __new__(cls, *args, **kwargs):
|
||||
self = super(vFloat, cls).__new__(cls, *args, **kwargs)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
return compat.unicode_type(self).encode('utf-8')
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
try:
|
||||
return cls(ical)
|
||||
except:
|
||||
raise ValueError('Expected float value, got: %s' % ical)
|
||||
|
||||
|
||||
class vInt(int):
|
||||
"""Just an int.
|
||||
"""
|
||||
def __new__(cls, *args, **kwargs):
|
||||
self = super(vInt, cls).__new__(cls, *args, **kwargs)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
return compat.unicode_type(self).encode('utf-8')
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
try:
|
||||
return cls(ical)
|
||||
except:
|
||||
raise ValueError('Expected int, got: %s' % ical)
|
||||
|
||||
|
||||
class vDDDLists(object):
|
||||
"""A list of vDDDTypes values.
|
||||
"""
|
||||
def __init__(self, dt_list):
|
||||
if not hasattr(dt_list, '__iter__'):
|
||||
dt_list = [dt_list]
|
||||
vDDD = []
|
||||
tzid = None
|
||||
for dt in dt_list:
|
||||
dt = vDDDTypes(dt)
|
||||
vDDD.append(dt)
|
||||
if 'TZID' in dt.params:
|
||||
tzid = dt.params['TZID']
|
||||
|
||||
if tzid:
|
||||
# NOTE: no support for multiple timezones here!
|
||||
self.params = Parameters({'TZID': tzid})
|
||||
self.dts = vDDD
|
||||
|
||||
def to_ical(self):
|
||||
dts_ical = (dt.to_ical() for dt in self.dts)
|
||||
return b",".join(dts_ical)
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical, timezone=None):
|
||||
out = []
|
||||
ical_dates = ical.split(",")
|
||||
for ical_dt in ical_dates:
|
||||
out.append(vDDDTypes.from_ical(ical_dt, timezone=timezone))
|
||||
return out
|
||||
|
||||
|
||||
class vDDDTypes(object):
|
||||
"""A combined Datetime, Date or Duration parser/generator. Their format
|
||||
cannot be confused, and often values can be of either types.
|
||||
So this is practical.
|
||||
"""
|
||||
def __init__(self, dt):
|
||||
if not isinstance(dt, (datetime, date, timedelta, time)):
|
||||
raise ValueError('You must use datetime, date, timedelta or time')
|
||||
if isinstance(dt, datetime):
|
||||
self.params = Parameters(dict(value='DATE-TIME'))
|
||||
elif isinstance(dt, date):
|
||||
self.params = Parameters(dict(value='DATE'))
|
||||
elif isinstance(dt, time):
|
||||
self.params = Parameters(dict(value='TIME'))
|
||||
|
||||
if (isinstance(dt, datetime) or isinstance(dt, time))\
|
||||
and getattr(dt, 'tzinfo', False):
|
||||
tzinfo = dt.tzinfo
|
||||
if tzinfo is not pytz.utc and not isinstance(tzinfo, tzutc):
|
||||
# set the timezone as a parameter to the property
|
||||
tzid = tzid_from_dt(dt)
|
||||
if tzid:
|
||||
self.params.update({'TZID': tzid})
|
||||
self.dt = dt
|
||||
|
||||
def to_ical(self):
|
||||
dt = self.dt
|
||||
if isinstance(dt, datetime):
|
||||
return vDatetime(dt).to_ical()
|
||||
elif isinstance(dt, date):
|
||||
return vDate(dt).to_ical()
|
||||
elif isinstance(dt, timedelta):
|
||||
return vDuration(dt).to_ical()
|
||||
elif isinstance(dt, time):
|
||||
return vTime(dt).to_ical()
|
||||
else:
|
||||
raise ValueError('Unknown date type')
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical, timezone=None):
|
||||
if isinstance(ical, cls):
|
||||
return ical.dt
|
||||
u = ical.upper()
|
||||
if u.startswith('-P') or u.startswith('P'):
|
||||
return vDuration.from_ical(ical)
|
||||
try:
|
||||
return vDatetime.from_ical(ical, timezone=timezone)
|
||||
except ValueError:
|
||||
try:
|
||||
return vDate.from_ical(ical)
|
||||
except ValueError:
|
||||
return vTime.from_ical(ical)
|
||||
|
||||
|
||||
class vDate(object):
|
||||
"""Render and generates iCalendar date format.
|
||||
"""
|
||||
def __init__(self, dt):
|
||||
if not isinstance(dt, date):
|
||||
raise ValueError('Value MUST be a date instance')
|
||||
self.dt = dt
|
||||
self.params = Parameters(dict(value='DATE'))
|
||||
|
||||
def to_ical(self):
|
||||
s = "%04d%02d%02d" % (self.dt.year, self.dt.month, self.dt.day)
|
||||
return s.encode('utf-8')
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical):
|
||||
try:
|
||||
timetuple = (
|
||||
int(ical[:4]), # year
|
||||
int(ical[4:6]), # month
|
||||
int(ical[6:8]), # day
|
||||
)
|
||||
return date(*timetuple)
|
||||
except:
|
||||
raise ValueError('Wrong date format %s' % ical)
|
||||
|
||||
|
||||
class vDatetime(object):
|
||||
"""Render and generates icalendar datetime format.
|
||||
|
||||
vDatetime is timezone aware and uses the pytz library, an implementation of
|
||||
the Olson database in Python. When a vDatetime object is created from an
|
||||
ical string, you can pass a valid pytz timezone identifier. When a
|
||||
vDatetime object is created from a python datetime object, it uses the
|
||||
tzinfo component, if present. Otherwise an timezone-naive object is
|
||||
created. Be aware that there are certain limitations with timezone naive
|
||||
DATE-TIME components in the icalendar standard.
|
||||
"""
|
||||
def __init__(self, dt):
|
||||
self.dt = dt
|
||||
self.params = Parameters()
|
||||
|
||||
def to_ical(self):
|
||||
dt = self.dt
|
||||
tzid = tzid_from_dt(dt)
|
||||
|
||||
s = "%04d%02d%02dT%02d%02d%02d" % (
|
||||
dt.year,
|
||||
dt.month,
|
||||
dt.day,
|
||||
dt.hour,
|
||||
dt.minute,
|
||||
dt.second
|
||||
)
|
||||
if tzid == 'UTC':
|
||||
s += "Z"
|
||||
elif tzid:
|
||||
self.params.update({'TZID': tzid})
|
||||
return s.encode('utf-8')
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical, timezone=None):
|
||||
tzinfo = None
|
||||
if timezone:
|
||||
try:
|
||||
tzinfo = pytz.timezone(timezone)
|
||||
except pytz.UnknownTimeZoneError:
|
||||
pass
|
||||
|
||||
try:
|
||||
timetuple = (
|
||||
int(ical[:4]), # year
|
||||
int(ical[4:6]), # month
|
||||
int(ical[6:8]), # day
|
||||
int(ical[9:11]), # hour
|
||||
int(ical[11:13]), # minute
|
||||
int(ical[13:15]), # second
|
||||
)
|
||||
if tzinfo:
|
||||
return tzinfo.localize(datetime(*timetuple))
|
||||
elif not ical[15:]:
|
||||
return datetime(*timetuple)
|
||||
elif ical[15:16] == 'Z':
|
||||
return datetime(tzinfo=pytz.utc, *timetuple)
|
||||
else:
|
||||
raise ValueError(ical)
|
||||
except:
|
||||
raise ValueError('Wrong datetime format: %s' % ical)
|
||||
|
||||
|
||||
class vDuration(object):
|
||||
"""Subclass of timedelta that renders itself in the iCalendar DURATION
|
||||
format.
|
||||
"""
|
||||
|
||||
def __init__(self, td):
|
||||
if not isinstance(td, timedelta):
|
||||
raise ValueError('Value MUST be a timedelta instance')
|
||||
self.td = td
|
||||
self.params = Parameters()
|
||||
|
||||
def to_ical(self):
|
||||
sign = ""
|
||||
if self.td.days < 0:
|
||||
sign = "-"
|
||||
self.td = -self.td
|
||||
timepart = ""
|
||||
if self.td.seconds:
|
||||
timepart = "T"
|
||||
hours = self.td.seconds // 3600
|
||||
minutes = self.td.seconds % 3600 // 60
|
||||
seconds = self.td.seconds % 60
|
||||
if hours:
|
||||
timepart += "%dH" % hours
|
||||
if minutes or (hours and seconds):
|
||||
timepart += "%dM" % minutes
|
||||
if seconds:
|
||||
timepart += "%dS" % seconds
|
||||
if self.td.days == 0 and timepart:
|
||||
return (compat.unicode_type(sign).encode('utf-8') + b'P' +
|
||||
compat.unicode_type(timepart).encode('utf-8'))
|
||||
else:
|
||||
return (compat.unicode_type(sign).encode('utf-8') + b'P' +
|
||||
compat.unicode_type(abs(self.td.days)).encode('utf-8') +
|
||||
b'D' + compat.unicode_type(timepart).encode('utf-8'))
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical):
|
||||
try:
|
||||
match = DURATION_REGEX.match(ical)
|
||||
sign, weeks, days, hours, minutes, seconds = match.groups()
|
||||
if weeks:
|
||||
value = timedelta(weeks=int(weeks))
|
||||
else:
|
||||
value = timedelta(days=int(days or 0),
|
||||
hours=int(hours or 0),
|
||||
minutes=int(minutes or 0),
|
||||
seconds=int(seconds or 0))
|
||||
if sign == '-':
|
||||
value = -value
|
||||
return value
|
||||
except:
|
||||
raise ValueError('Invalid iCalendar duration: %s' % ical)
|
||||
|
||||
|
||||
class vPeriod(object):
|
||||
"""A precise period of time.
|
||||
"""
|
||||
def __init__(self, per):
|
||||
start, end_or_duration = per
|
||||
if not (isinstance(start, datetime) or isinstance(start, date)):
|
||||
raise ValueError('Start value MUST be a datetime or date instance')
|
||||
if not (isinstance(end_or_duration, datetime) or
|
||||
isinstance(end_or_duration, date) or
|
||||
isinstance(end_or_duration, timedelta)):
|
||||
raise ValueError('end_or_duration MUST be a datetime, '
|
||||
'date or timedelta instance')
|
||||
by_duration = 0
|
||||
if isinstance(end_or_duration, timedelta):
|
||||
by_duration = 1
|
||||
duration = end_or_duration
|
||||
end = start + duration
|
||||
else:
|
||||
end = end_or_duration
|
||||
duration = end - start
|
||||
if start > end:
|
||||
raise ValueError("Start time is greater than end time")
|
||||
|
||||
self.params = Parameters()
|
||||
# set the timezone identifier
|
||||
# does not support different timezones for start and end
|
||||
tzid = tzid_from_dt(start)
|
||||
if tzid:
|
||||
self.params['TZID'] = tzid
|
||||
|
||||
self.start = start
|
||||
self.end = end
|
||||
self.by_duration = by_duration
|
||||
self.duration = duration
|
||||
|
||||
def __cmp__(self, other):
|
||||
if not isinstance(other, vPeriod):
|
||||
raise NotImplementedError('Cannot compare vPeriod with %r' % other)
|
||||
return cmp((self.start, self.end), (other.start, other.end))
|
||||
|
||||
def overlaps(self, other):
|
||||
if self.start > other.start:
|
||||
return other.overlaps(self)
|
||||
if self.start <= other.start < self.end:
|
||||
return True
|
||||
return False
|
||||
|
||||
def to_ical(self):
|
||||
if self.by_duration:
|
||||
return (vDatetime(self.start).to_ical() + b'/' +
|
||||
vDuration(self.duration).to_ical())
|
||||
return (vDatetime(self.start).to_ical() + b'/' +
|
||||
vDatetime(self.end).to_ical())
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical):
|
||||
try:
|
||||
start, end_or_duration = ical.split('/')
|
||||
start = vDDDTypes.from_ical(start)
|
||||
end_or_duration = vDDDTypes.from_ical(end_or_duration)
|
||||
return (start, end_or_duration)
|
||||
except:
|
||||
raise ValueError('Expected period format, got: %s' % ical)
|
||||
|
||||
def __repr__(self):
|
||||
if self.by_duration:
|
||||
p = (self.start, self.duration)
|
||||
else:
|
||||
p = (self.start, self.end)
|
||||
return 'vPeriod(%r)' % p
|
||||
|
||||
|
||||
class vWeekday(compat.unicode_type):
|
||||
"""This returns an unquoted weekday abbrevation.
|
||||
"""
|
||||
week_days = CaselessDict({
|
||||
"SU": 0, "MO": 1, "TU": 2, "WE": 3, "TH": 4, "FR": 5, "SA": 6,
|
||||
})
|
||||
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
self = super(vWeekday, cls).__new__(cls, value)
|
||||
match = WEEKDAY_RULE.match(self)
|
||||
if match is None:
|
||||
raise ValueError('Expected weekday abbrevation, got: %s' % self)
|
||||
match = match.groupdict()
|
||||
sign = match['signal']
|
||||
weekday = match['weekday']
|
||||
relative = match['relative']
|
||||
if not weekday in vWeekday.week_days or sign not in '+-':
|
||||
raise ValueError('Expected weekday abbrevation, got: %s' % self)
|
||||
self.relative = relative and int(relative) or None
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
return self.encode(DEFAULT_ENCODING).upper()
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
try:
|
||||
return cls(ical.upper())
|
||||
except:
|
||||
raise ValueError('Expected weekday abbrevation, got: %s' % ical)
|
||||
|
||||
|
||||
class vFrequency(compat.unicode_type):
|
||||
"""A simple class that catches illegal values.
|
||||
"""
|
||||
|
||||
frequencies = CaselessDict({
|
||||
"SECONDLY": "SECONDLY",
|
||||
"MINUTELY": "MINUTELY",
|
||||
"HOURLY": "HOURLY",
|
||||
"DAILY": "DAILY",
|
||||
"WEEKLY": "WEEKLY",
|
||||
"MONTHLY": "MONTHLY",
|
||||
"YEARLY": "YEARLY",
|
||||
})
|
||||
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
self = super(vFrequency, cls).__new__(cls, value)
|
||||
if not self in vFrequency.frequencies:
|
||||
raise ValueError('Expected frequency, got: %s' % self)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
return self.encode(DEFAULT_ENCODING).upper()
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
try:
|
||||
return cls(ical.upper())
|
||||
except:
|
||||
raise ValueError('Expected frequency, got: %s' % ical)
|
||||
|
||||
|
||||
class vRecur(CaselessDict):
|
||||
"""Recurrence definition.
|
||||
"""
|
||||
|
||||
frequencies = ["SECONDLY", "MINUTELY", "HOURLY", "DAILY", "WEEKLY",
|
||||
"MONTHLY", "YEARLY"]
|
||||
|
||||
# Mac iCal ignores RRULEs where FREQ is not the first rule part.
|
||||
# Sorts parts according to the order listed in RFC 5545, section 3.3.10.
|
||||
canonical_order = ("FREQ", "UNTIL", "COUNT", "INTERVAL",
|
||||
"BYSECOND", "BYMINUTE", "BYHOUR", "BYDAY",
|
||||
"BYMONTHDAY", "BYYEARDAY", "BYWEEKNO", "BYMONTH",
|
||||
"BYSETPOS", "WKST")
|
||||
|
||||
types = CaselessDict({
|
||||
'COUNT': vInt,
|
||||
'INTERVAL': vInt,
|
||||
'BYSECOND': vInt,
|
||||
'BYMINUTE': vInt,
|
||||
'BYHOUR': vInt,
|
||||
'BYMONTHDAY': vInt,
|
||||
'BYYEARDAY': vInt,
|
||||
'BYMONTH': vInt,
|
||||
'UNTIL': vDDDTypes,
|
||||
'BYSETPOS': vInt,
|
||||
'WKST': vWeekday,
|
||||
'BYDAY': vWeekday,
|
||||
'FREQ': vFrequency,
|
||||
})
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
CaselessDict.__init__(self, *args, **kwargs)
|
||||
self.params = Parameters()
|
||||
|
||||
def to_ical(self):
|
||||
result = []
|
||||
for key, vals in self.sorted_items():
|
||||
typ = self.types[key]
|
||||
if not isinstance(vals, SEQUENCE_TYPES):
|
||||
vals = [vals]
|
||||
vals = b','.join(typ(val).to_ical() for val in vals)
|
||||
|
||||
# CaselessDict keys are always unicode
|
||||
key = key.encode(DEFAULT_ENCODING)
|
||||
result.append(key + b'=' + vals)
|
||||
|
||||
return b';'.join(result)
|
||||
|
||||
@classmethod
|
||||
def parse_type(cls, key, values):
|
||||
# integers
|
||||
parser = cls.types.get(key, vText)
|
||||
return [parser.from_ical(v) for v in values.split(',')]
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
if isinstance(ical, cls):
|
||||
return ical
|
||||
try:
|
||||
recur = cls()
|
||||
for pairs in ical.split(';'):
|
||||
key, vals = pairs.split('=')
|
||||
recur[key] = cls.parse_type(key, vals)
|
||||
return dict(recur)
|
||||
except:
|
||||
raise ValueError('Error in recurrence rule: %s' % ical)
|
||||
|
||||
|
||||
class vText(compat.unicode_type):
|
||||
"""Simple text.
|
||||
"""
|
||||
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
self = super(vText, cls).__new__(cls, value)
|
||||
self.encoding = encoding
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "vText('%s')" % self.to_ical()
|
||||
|
||||
def to_ical(self):
|
||||
return escape_char(self).encode(self.encoding)
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
ical_unesc = unescape_char(ical)
|
||||
return cls(ical_unesc)
|
||||
|
||||
|
||||
class vTime(object):
|
||||
"""Render and generates iCalendar time format.
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
if len(args) == 1:
|
||||
if not isinstance(args[0], (time, datetime)):
|
||||
raise ValueError('Expected a datetime.time, got: %s' % args[0])
|
||||
self.dt = args[0]
|
||||
else:
|
||||
self.dt = time(*args)
|
||||
self.params = Parameters(dict(value='TIME'))
|
||||
|
||||
def to_ical(self):
|
||||
return self.dt.strftime("%H%M%S")
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical):
|
||||
# TODO: timezone support
|
||||
try:
|
||||
timetuple = (int(ical[:2]), int(ical[2:4]), int(ical[4:6]))
|
||||
return time(*timetuple)
|
||||
except:
|
||||
raise ValueError('Expected time, got: %s' % ical)
|
||||
|
||||
|
||||
class vUri(compat.unicode_type):
|
||||
"""Uniform resource identifier is basically just an unquoted string.
|
||||
"""
|
||||
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
self = super(vUri, cls).__new__(cls, value)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
return self.encode(DEFAULT_ENCODING)
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
try:
|
||||
return cls(ical)
|
||||
except:
|
||||
raise ValueError('Expected , got: %s' % ical)
|
||||
|
||||
|
||||
class vGeo(object):
|
||||
"""A special type that is only indirectly defined in the rfc.
|
||||
"""
|
||||
|
||||
def __init__(self, geo):
|
||||
try:
|
||||
latitude, longitude = (geo[0], geo[1])
|
||||
latitude = float(latitude)
|
||||
longitude = float(longitude)
|
||||
except:
|
||||
raise ValueError('Input must be (float, float) for '
|
||||
'latitude and longitude')
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.params = Parameters()
|
||||
|
||||
def to_ical(self):
|
||||
return '%s;%s' % (self.latitude, self.longitude)
|
||||
|
||||
@staticmethod
|
||||
def from_ical(ical):
|
||||
try:
|
||||
latitude, longitude = ical.split(';')
|
||||
return (float(latitude), float(longitude))
|
||||
except:
|
||||
raise ValueError("Expected 'float;float' , got: %s" % ical)
|
||||
|
||||
|
||||
class vUTCOffset(object):
|
||||
"""Renders itself as a utc offset.
|
||||
"""
|
||||
|
||||
def __init__(self, td):
|
||||
if not isinstance(td, timedelta):
|
||||
raise ValueError('Offset value MUST be a timedelta instance')
|
||||
self.td = td
|
||||
self.params = Parameters()
|
||||
|
||||
def to_ical(self):
|
||||
|
||||
if self.td < timedelta(0):
|
||||
sign = '-%s'
|
||||
td = timedelta(0)-self.td # get timedelta relative to 0
|
||||
else:
|
||||
# Google Calendar rejects '0000' but accepts '+0000'
|
||||
sign = '+%s'
|
||||
td = self.td
|
||||
|
||||
days, seconds = td.days, td.seconds
|
||||
|
||||
hours = abs(days * 24 + seconds // 3600)
|
||||
minutes = abs((seconds % 3600) // 60)
|
||||
seconds = abs(seconds % 60)
|
||||
if seconds:
|
||||
duration = '%02i%02i%02i' % (hours, minutes, seconds)
|
||||
else:
|
||||
duration = '%02i%02i' % (hours, minutes)
|
||||
return sign % duration
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
if isinstance(ical, cls):
|
||||
return ical.td
|
||||
try:
|
||||
sign, hours, minutes, seconds = (ical[0:1],
|
||||
int(ical[1:3]),
|
||||
int(ical[3:5]),
|
||||
int(ical[5:7] or 0))
|
||||
offset = timedelta(hours=hours, minutes=minutes, seconds=seconds)
|
||||
except:
|
||||
raise ValueError('Expected utc offset, got: %s' % ical)
|
||||
if offset >= timedelta(hours=24):
|
||||
raise ValueError(
|
||||
'Offset must be less than 24 hours, was %s' % ical)
|
||||
if sign == '-':
|
||||
return -offset
|
||||
return offset
|
||||
|
||||
|
||||
class vInline(compat.unicode_type):
|
||||
"""This is an especially dumb class that just holds raw unparsed text and
|
||||
has parameters. Conversion of inline values are handled by the Component
|
||||
class, so no further processing is needed.
|
||||
"""
|
||||
def __new__(cls, value, encoding=DEFAULT_ENCODING):
|
||||
value = to_unicode(value, encoding=encoding)
|
||||
self = super(vInline, cls).__new__(cls, value)
|
||||
self.params = Parameters()
|
||||
return self
|
||||
|
||||
def to_ical(self):
|
||||
return self.encode(DEFAULT_ENCODING)
|
||||
|
||||
@classmethod
|
||||
def from_ical(cls, ical):
|
||||
return cls(ical)
|
||||
|
||||
|
||||
class TypesFactory(CaselessDict):
|
||||
"""All Value types defined in rfc 2445 are registered in this factory
|
||||
class.
|
||||
|
||||
The value and parameter names don't overlap. So one factory is enough for
|
||||
both kinds.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Set keys to upper for initial dict"
|
||||
CaselessDict.__init__(self, *args, **kwargs)
|
||||
self.all_types = (
|
||||
vBinary,
|
||||
vBoolean,
|
||||
vCalAddress,
|
||||
vDDDLists,
|
||||
vDDDTypes,
|
||||
vDate,
|
||||
vDatetime,
|
||||
vDuration,
|
||||
vFloat,
|
||||
vFrequency,
|
||||
vGeo,
|
||||
vInline,
|
||||
vInt,
|
||||
vPeriod,
|
||||
vRecur,
|
||||
vText,
|
||||
vTime,
|
||||
vUTCOffset,
|
||||
vUri,
|
||||
vWeekday
|
||||
)
|
||||
self['binary'] = vBinary
|
||||
self['boolean'] = vBoolean
|
||||
self['cal-address'] = vCalAddress
|
||||
self['date'] = vDDDTypes
|
||||
self['date-time'] = vDDDTypes
|
||||
self['duration'] = vDDDTypes
|
||||
self['float'] = vFloat
|
||||
self['integer'] = vInt
|
||||
self['period'] = vPeriod
|
||||
self['recur'] = vRecur
|
||||
self['text'] = vText
|
||||
self['time'] = vTime
|
||||
self['uri'] = vUri
|
||||
self['utc-offset'] = vUTCOffset
|
||||
self['geo'] = vGeo
|
||||
self['inline'] = vInline
|
||||
self['date-time-list'] = vDDDLists
|
||||
|
||||
#################################################
|
||||
# Property types
|
||||
|
||||
# These are the default types
|
||||
types_map = CaselessDict({
|
||||
####################################
|
||||
# Property value types
|
||||
# Calendar Properties
|
||||
'calscale': 'text',
|
||||
'method': 'text',
|
||||
'prodid': 'text',
|
||||
'version': 'text',
|
||||
# Descriptive Component Properties
|
||||
'attach': 'uri',
|
||||
'categories': 'text',
|
||||
'class': 'text',
|
||||
'comment': 'text',
|
||||
'description': 'text',
|
||||
'geo': 'geo',
|
||||
'location': 'text',
|
||||
'percent-complete': 'integer',
|
||||
'priority': 'integer',
|
||||
'resources': 'text',
|
||||
'status': 'text',
|
||||
'summary': 'text',
|
||||
# Date and Time Component Properties
|
||||
'completed': 'date-time',
|
||||
'dtend': 'date-time',
|
||||
'due': 'date-time',
|
||||
'dtstart': 'date-time',
|
||||
'duration': 'duration',
|
||||
'freebusy': 'period',
|
||||
'transp': 'text',
|
||||
# Time Zone Component Properties
|
||||
'tzid': 'text',
|
||||
'tzname': 'text',
|
||||
'tzoffsetfrom': 'utc-offset',
|
||||
'tzoffsetto': 'utc-offset',
|
||||
'tzurl': 'uri',
|
||||
# Relationship Component Properties
|
||||
'attendee': 'cal-address',
|
||||
'contact': 'text',
|
||||
'organizer': 'cal-address',
|
||||
'recurrence-id': 'date-time',
|
||||
'related-to': 'text',
|
||||
'url': 'uri',
|
||||
'uid': 'text',
|
||||
# Recurrence Component Properties
|
||||
'exdate': 'date-time-list',
|
||||
'exrule': 'recur',
|
||||
'rdate': 'date-time-list',
|
||||
'rrule': 'recur',
|
||||
# Alarm Component Properties
|
||||
'action': 'text',
|
||||
'repeat': 'integer',
|
||||
'trigger': 'duration',
|
||||
# Change Management Component Properties
|
||||
'created': 'date-time',
|
||||
'dtstamp': 'date-time',
|
||||
'last-modified': 'date-time',
|
||||
'sequence': 'integer',
|
||||
# Miscellaneous Component Properties
|
||||
'request-status': 'text',
|
||||
####################################
|
||||
# parameter types (luckily there is no name overlap)
|
||||
'altrep': 'uri',
|
||||
'cn': 'text',
|
||||
'cutype': 'text',
|
||||
'delegated-from': 'cal-address',
|
||||
'delegated-to': 'cal-address',
|
||||
'dir': 'uri',
|
||||
'encoding': 'text',
|
||||
'fmttype': 'text',
|
||||
'fbtype': 'text',
|
||||
'language': 'text',
|
||||
'member': 'cal-address',
|
||||
'partstat': 'text',
|
||||
'range': 'text',
|
||||
'related': 'text',
|
||||
'reltype': 'text',
|
||||
'role': 'text',
|
||||
'rsvp': 'boolean',
|
||||
'sent-by': 'cal-address',
|
||||
'tzid': 'text',
|
||||
'value': 'text',
|
||||
})
|
||||
|
||||
def for_property(self, name):
|
||||
"""Returns a the default type for a property or parameter
|
||||
"""
|
||||
return self[self.types_map.get(name, 'text')]
|
||||
|
||||
def to_ical(self, name, value):
|
||||
"""Encodes a named value from a primitive python type to an icalendar
|
||||
encoded string.
|
||||
"""
|
||||
type_class = self.for_property(name)
|
||||
return type_class(value).to_ical()
|
||||
|
||||
def from_ical(self, name, value):
|
||||
"""Decodes a named property or parameter value from an icalendar
|
||||
encoded string to a primitive python type.
|
||||
"""
|
||||
type_class = self.for_property(name)
|
||||
decoded = type_class.from_ical(value)
|
||||
return decoded
|
5
libs/icalendar-3.6.1/src/icalendar/tests/__init__.py
Normal file
5
libs/icalendar-3.6.1/src/icalendar/tests/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
# unittest/unittest2 importer
|
||||
import unittest
|
||||
if not hasattr(unittest.TestCase, 'assertIsNotNone'):
|
||||
import unittest2 as unittest
|
||||
unittest # pep 8
|
16
libs/icalendar-3.6.1/src/icalendar/tests/encoding.ics
Normal file
16
libs/icalendar-3.6.1/src/icalendar/tests/encoding.ics
Normal file
@ -0,0 +1,16 @@
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Plönë.org//NONSGML plone.app.event//EN
|
||||
VERSION:2.0
|
||||
X-WR-CALNAME:äöü ÄÖÜ €
|
||||
X-WR-CALDESC:test non ascii: äöü ÄÖÜ €
|
||||
X-WR-RELCALID:12345
|
||||
BEGIN:VEVENT
|
||||
DTSTART:20101010T100000Z
|
||||
DTEND:20101010T120000Z
|
||||
CREATED:20101010T100000Z
|
||||
UID:123456
|
||||
SUMMARY:Non-ASCII Test: ÄÖÜ äöü €
|
||||
DESCRIPTION:icalendar should be able to handle non-ascii: €äüöÄÜÖ.
|
||||
LOCATION:Tribstrül
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
@ -0,0 +1,48 @@
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Google Inc//Google Calendar 70.9054//EN
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
X-WR-CALNAME:Market East
|
||||
X-WR-TIMEZONE:America/New_York
|
||||
X-WR-CALDESC:
|
||||
BEGIN:VTIMEZONE
|
||||
TZID: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
|
||||
DTSTART;TZID=America/New_York:20130907T120000
|
||||
DTEND;TZID=America/New_York:20130907T170000
|
||||
RRULE:FREQ=WEEKLY;BYDAY=FR,SA;UNTIL=20131025T035959Z
|
||||
EXDATE;TZID=America/New_York:20131012T120000
|
||||
EXDATE;TZID=America/New_York:20131011T120000
|
||||
DTSTAMP:20131021T025552Z
|
||||
UID:ak30b02u7858q1oo6ji9dm4mgg@google.com
|
||||
CREATED:20130903T181453Z
|
||||
DESCRIPTION:The Fieldhouse and Hard Rock Cafe are working with PhillyRising
|
||||
to provide live entertainment on Friday and Saturday afternoons throughout
|
||||
the Summer.
|
||||
LAST-MODIFIED:20131015T210927Z
|
||||
LOCATION:12th and Market Streets (weather permitting)
|
||||
SEQUENCE:0
|
||||
STATUS:CONFIRMED
|
||||
SUMMARY:Market East Live!
|
||||
TRANSP:OPAQUE
|
||||
END:VEVENT
|
||||
|
||||
END:VCALENDAR
|
@ -0,0 +1,41 @@
|
||||
BEGIN:VEVENT
|
||||
DTSTART:20130927T130000Z
|
||||
DTEND:20130927T140000Z
|
||||
DTSTAMP:20131107T004757Z
|
||||
ORGANIZER;CN=gxxxxxxxn@nxx.fr:mailto:gxxxxxn@nxx.fr
|
||||
UID:040000008200E00074C5B7101A82E00800000000A0F3321606B6CE01000000000000000
|
||||
010000000F09F33F0E8ED4C44B99F6027ACF588D0
|
||||
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=St
|
||||
eve Bxxxxxx;X-NUM-GUESTS=0:mailto:sxxxxxt@nxx.fr
|
||||
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Boris
|
||||
Hxxxxx;X-NUM-GUESTS=0:mailto:bxxxxxxk@vxxxxxxxx.com
|
||||
CREATED:20130920T113409Z
|
||||
DESCRIPTION:Quand : vendredi 27 septembre 2013 15:00-16:00 (UTC+01:00) Brux
|
||||
elles\, Copenhague\, Madrid\, Paris.\nEmplacement : Conf-Call - 01 xx xx xx
|
||||
xx\n\nRemarque : le décalage GMT ci-dessus ne tient pas compte des réglage
|
||||
s de l'heure d'été.\n\n*~*~*~*~*~*~*~*~*~*\n\nComme convenu à l’instant par
|
||||
e-mail\n
|
||||
LAST-MODIFIED:20130920T115104Z
|
||||
LOCATION:Conf-Call - 01 xx xx xx xx
|
||||
SEQUENCE:0
|
||||
STATUS:CONFIRMED
|
||||
SUMMARY:Nxx - Réunion lancement PxxxxxxT
|
||||
TRANSP:OPAQUE
|
||||
X-ALT-DESC;FMTTYPE=text/html:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//E
|
||||
N">\n<HTML>\n<HEAD>\n<META NAME="Generator" CONTENT="MS Exchange Server ver
|
||||
sion 08.00.0681.000">\n<TITLE></TITLE>\n</HEAD>\n<BODY>\n<!-- Converted fro
|
||||
m text/rtf format -->\n\n<P DIR=LTR><SPAN LANG="fr"><FONT FACE="Calibri">Qu
|
||||
and : vendredi 27 septembre 2013 15:00-16:00 (UTC+01:00) Bruxelles\, Copenh
|
||||
ague\, Madrid\, Paris.</FONT></SPAN></P>\n\n<P DIR=LTR><SPAN LANG="fr"><FON
|
||||
T FACE="Calibri">Emplacement : Conf-Call - 01 xx xx xx xx</FONT></SPAN></P>
|
||||
\n\n<P DIR=LTR><SPAN LANG="fr"><FONT FACE="Calibri">Remarque : le décalage
|
||||
GMT ci-dessus ne tient pas compte des réglages de l'heure d'été.</FONT></SP
|
||||
AN></P>\n\n<P DIR=LTR><SPAN LANG="fr"><FONT FACE="Calibri">*~*~*~*~*~*~*~*~
|
||||
*~*</FONT></SPAN></P>\n\n<P DIR=LTR><SPAN LANG="fr"><FONT FACE="Calibri">Co
|
||||
mme convenu à l’instant par e-mail</FONT></SPAN><SPAN LANG="fr"></SPAN></P>
|
||||
\n\n</BODY>\n</HTML>
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
|
||||
X
|
||||
END:VEVENT
|
@ -0,0 +1,78 @@
|
||||
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
|
80
libs/icalendar-3.6.1/src/icalendar/tests/multiple.ics
Normal file
80
libs/icalendar-3.6.1/src/icalendar/tests/multiple.ics
Normal file
@ -0,0 +1,80 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION
|
||||
|
||||
:2.0
|
||||
PRODID
|
||||
|
||||
:-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN
|
||||
METHOD
|
||||
|
||||
:PUBLISH
|
||||
BEGIN:VEVENT
|
||||
UID
|
||||
|
||||
:956630271
|
||||
SUMMARY
|
||||
|
||||
:Christmas Day
|
||||
CLASS
|
||||
|
||||
:PUBLIC
|
||||
X-MOZILLA-ALARM-DEFAULT-UNITS
|
||||
|
||||
:minutes
|
||||
X-MOZILLA-ALARM-DEFAULT-LENGTH
|
||||
|
||||
:15
|
||||
X-MOZILLA-RECUR-DEFAULT-UNITS
|
||||
|
||||
:weeks
|
||||
X-MOZILLA-RECUR-DEFAULT-INTERVAL
|
||||
|
||||
:1
|
||||
DTSTART
|
||||
|
||||
;VALUE=DATE
|
||||
:20031225
|
||||
DTEND
|
||||
|
||||
;VALUE=DATE
|
||||
:20031226
|
||||
DTSTAMP
|
||||
|
||||
:20020430T114937Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
BEGIN:VCALENDAR
|
||||
VERSION
|
||||
:2.0
|
||||
PRODID
|
||||
:-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN
|
||||
METHOD
|
||||
:PUBLISH
|
||||
BEGIN:VEVENT
|
||||
UID
|
||||
:911737808
|
||||
SUMMARY
|
||||
:Boxing Day
|
||||
CLASS
|
||||
:PUBLIC
|
||||
X-MOZILLA-ALARM-DEFAULT-UNITS
|
||||
:minutes
|
||||
X-MOZILLA-ALARM-DEFAULT-LENGTH
|
||||
:15
|
||||
X-MOZILLA-RECUR-DEFAULT-UNITS
|
||||
:weeks
|
||||
X-MOZILLA-RECUR-DEFAULT-INTERVAL
|
||||
:1
|
||||
DTSTART
|
||||
;VALUE=DATE
|
||||
:20030501
|
||||
DTSTAMP
|
||||
:20020430T114937Z
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID
|
||||
:wh4t3v3r
|
||||
DTSTART;VALUE=DATE:20031225
|
||||
SUMMARY:Christmas again!
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
24
libs/icalendar-3.6.1/src/icalendar/tests/recurrence.ics
Normal file
24
libs/icalendar-3.6.1/src/icalendar/tests/recurrence.ics
Normal file
@ -0,0 +1,24 @@
|
||||
BEGIN:VCALENDAR
|
||||
METHOD:Request
|
||||
PRODID:-//My product//mxm.dk/
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
DTSTART:19960401T010000
|
||||
DTEND:19960401T020000
|
||||
RRULE:FREQ=DAILY;COUNT=100
|
||||
EXDATE:19960402T010000Z,19960403T010000Z,19960404T010000Z
|
||||
SUMMARY:A recurring event with exdates
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTART;TZID=Europe/Vienna:20120327T100000
|
||||
DTEND;TZID=Europe/Vienna:20120327T180000
|
||||
RRULE:FREQ=WEEKLY;UNTIL=20120703T080000Z;BYDAY=TU
|
||||
EXDATE;TZID=Europe/Vienna:20120529T100000
|
||||
EXDATE;TZID=Europe/Vienna:20120403T100000
|
||||
EXDATE;TZID=Europe/Vienna:20120410T100000
|
||||
EXDATE;TZID=Europe/Vienna:20120501T100000
|
||||
EXDATE;TZID=Europe/Vienna:20120417T100000
|
||||
DTSTAMP:20130716T120638Z
|
||||
SUMMARY:A Recurring event with multiple exdates, one per line.
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
90
libs/icalendar-3.6.1/src/icalendar/tests/test_encoding.py
Normal file
90
libs/icalendar-3.6.1/src/icalendar/tests/test_encoding.py
Normal file
@ -0,0 +1,90 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import datetime
|
||||
import icalendar
|
||||
import os
|
||||
import pytz
|
||||
|
||||
|
||||
class TestEncoding(unittest.TestCase):
|
||||
|
||||
def test_create_from_ical(self):
|
||||
directory = os.path.dirname(__file__)
|
||||
data = open(os.path.join(directory, 'encoding.ics'), 'rb').read()
|
||||
cal = icalendar.Calendar.from_ical(data)
|
||||
|
||||
self.assertEqual(cal['prodid'].to_ical().decode('utf-8'),
|
||||
u"-//Plönë.org//NONSGML plone.app.event//EN")
|
||||
self.assertEqual(cal['X-WR-CALDESC'].to_ical().decode('utf-8'),
|
||||
u"test non ascii: äöü ÄÖÜ €")
|
||||
|
||||
event = cal.walk('VEVENT')[0]
|
||||
self.assertEqual(event['SUMMARY'].to_ical().decode('utf-8'),
|
||||
u'Non-ASCII Test: ÄÖÜ äöü €')
|
||||
self.assertEqual(
|
||||
event['DESCRIPTION'].to_ical().decode('utf-8'),
|
||||
u'icalendar should be able to handle non-ascii: €äüöÄÜÖ.'
|
||||
)
|
||||
self.assertEqual(event['LOCATION'].to_ical().decode('utf-8'),
|
||||
u'Tribstrül')
|
||||
|
||||
def test_create_to_ical(self):
|
||||
cal = icalendar.Calendar()
|
||||
|
||||
cal.add('prodid', u"-//Plönë.org//NONSGML plone.app.event//EN")
|
||||
cal.add('version', u"2.0")
|
||||
cal.add('x-wr-calname', u"äöü ÄÖÜ €")
|
||||
cal.add('x-wr-caldesc', u"test non ascii: äöü ÄÖÜ €")
|
||||
cal.add('x-wr-relcalid', u"12345")
|
||||
|
||||
event = icalendar.Event()
|
||||
event.add(
|
||||
'dtstart',
|
||||
datetime.datetime(2010, 10, 10, 10, 00, 00, tzinfo=pytz.utc)
|
||||
)
|
||||
event.add(
|
||||
'dtend',
|
||||
datetime.datetime(2010, 10, 10, 12, 00, 00, tzinfo=pytz.utc)
|
||||
)
|
||||
event.add(
|
||||
'created',
|
||||
datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=pytz.utc)
|
||||
)
|
||||
event.add('uid', u'123456')
|
||||
event.add('summary', u'Non-ASCII Test: ÄÖÜ äöü €')
|
||||
event.add(
|
||||
'description',
|
||||
u'icalendar should be able to de/serialize non-ascii.'
|
||||
)
|
||||
event.add('location', u'Tribstrül')
|
||||
cal.add_component(event)
|
||||
|
||||
ical_lines = cal.to_ical().splitlines()
|
||||
cmp = b'PRODID:-//Pl\xc3\xb6n\xc3\xab.org//NONSGML plone.app.event//EN'
|
||||
self.assertTrue(cmp in ical_lines)
|
||||
|
||||
def test_create_event_simple(self):
|
||||
event = icalendar.Event()
|
||||
event.add(
|
||||
"dtstart",
|
||||
datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=pytz.utc)
|
||||
)
|
||||
event.add("summary", u"åäö")
|
||||
out = event.to_ical()
|
||||
summary = b'SUMMARY:\xc3\xa5\xc3\xa4\xc3\xb6'
|
||||
self.assertTrue(summary in out.splitlines())
|
||||
|
||||
def test_unicode_parameter_name(self):
|
||||
# Test for issue #80
|
||||
cal = icalendar.Calendar()
|
||||
event = icalendar.Event()
|
||||
event.add(u'DESCRIPTION', u'äöüßÄÖÜ')
|
||||
cal.add_component(event)
|
||||
c = cal.to_ical()
|
||||
self.assertEqual(
|
||||
c,
|
||||
b'BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDESCRIPTION:'
|
||||
+ b'\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f\xc3\x84\xc3\x96\xc3\x9c\r\n'
|
||||
+ b'END:VEVENT\r\nEND:VCALENDAR\r\n'
|
||||
)
|
247
libs/icalendar-3.6.1/src/icalendar/tests/test_fixed_issues.py
Normal file
247
libs/icalendar-3.6.1/src/icalendar/tests/test_fixed_issues.py
Normal file
@ -0,0 +1,247 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from icalendar.parser_tools import to_unicode
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import datetime
|
||||
import icalendar
|
||||
import os
|
||||
import pytz
|
||||
|
||||
|
||||
class TestIssues(unittest.TestCase):
|
||||
|
||||
def test_issue_53(self):
|
||||
"""Issue #53 - Parsing failure on some descriptions?
|
||||
https://github.com/collective/icalendar/issues/53
|
||||
"""
|
||||
|
||||
directory = os.path.dirname(__file__)
|
||||
ics = open(os.path.join(directory, 'issue_53_parsing_failure.ics'),
|
||||
'rb')
|
||||
cal = icalendar.Calendar.from_ical(ics.read())
|
||||
ics.close()
|
||||
|
||||
event = cal.walk('VEVENT')[0]
|
||||
desc = event.get('DESCRIPTION')
|
||||
self.assertTrue(b'July 12 at 6:30 PM' in desc.to_ical())
|
||||
|
||||
timezones = cal.walk('VTIMEZONE')
|
||||
self.assertEqual(len(timezones), 1)
|
||||
tz = timezones[0]
|
||||
self.assertEqual(tz['tzid'].to_ical(), b"America/New_York")
|
||||
|
||||
def test_issue_55(self):
|
||||
"""Issue #55 - Parse error on utc-offset with seconds value
|
||||
https://github.com/collective/icalendar/issues/55
|
||||
"""
|
||||
ical_str = """BEGIN:VTIMEZONE
|
||||
TZID:America/Los Angeles
|
||||
BEGIN:STANDARD
|
||||
DTSTART:18831118T120702
|
||||
RDATE:18831118T120702
|
||||
TZNAME:PST
|
||||
TZOFFSETFROM:-075258
|
||||
TZOFFSETTO:-0800
|
||||
END:STANDARD
|
||||
END:VTIMEZONE"""
|
||||
|
||||
tz = icalendar.Timezone.from_ical(ical_str)
|
||||
self.assertEqual(
|
||||
tz.to_ical(),
|
||||
b'BEGIN:VTIMEZONE\r\nTZID:America/Los Angeles\r\n'
|
||||
b'BEGIN:STANDARD\r\n'
|
||||
b'DTSTART:18831118T120702\r\nRDATE:18831118T120702\r\nTZNAME:PST'
|
||||
b'\r\nTZOFFSETFROM:-075258\r\nTZOFFSETTO:-0800\r\n'
|
||||
b'END:STANDARD\r\n'
|
||||
b'END:VTIMEZONE\r\n')
|
||||
|
||||
def test_issue_58(self):
|
||||
"""Issue #58 - TZID on UTC DATE-TIMEs
|
||||
https://github.com/collective/icalendar/issues/58
|
||||
"""
|
||||
|
||||
# According to RFC 2445: "The TZID property parameter MUST NOT be
|
||||
# applied to DATE-TIME or TIME properties whose time values are
|
||||
# specified in UTC."
|
||||
|
||||
event = icalendar.Event()
|
||||
dt = pytz.utc.localize(datetime.datetime(2012, 7, 16, 0, 0, 0))
|
||||
event.add('dtstart', dt)
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
b"BEGIN:VEVENT\r\n"
|
||||
b"DTSTART;VALUE=DATE-TIME:20120716T000000Z\r\n"
|
||||
b"END:VEVENT\r\n"
|
||||
)
|
||||
|
||||
def test_issue_64(self):
|
||||
"""Issue #64 - Event.to_ical() fails for unicode strings
|
||||
https://github.com/collective/icalendar/issues/64
|
||||
"""
|
||||
|
||||
# Non-unicode characters
|
||||
event = icalendar.Event()
|
||||
event.add("dtstart", datetime.datetime(2012, 9, 3, 0, 0, 0))
|
||||
event.add("summary", u"abcdef")
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:abcdef\r\nDTSTART;VALUE=DATE-TIME:"
|
||||
b"20120903T000000\r\nEND:VEVENT\r\n"
|
||||
)
|
||||
|
||||
# Unicode characters
|
||||
event = icalendar.Event()
|
||||
event.add("dtstart", datetime.datetime(2012, 9, 3, 0, 0, 0))
|
||||
event.add("summary", u"åäö")
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
b"BEGIN:VEVENT\r\nSUMMARY:\xc3\xa5\xc3\xa4\xc3\xb6\r\n"
|
||||
b"DTSTART;VALUE=DATE-TIME:20120903T000000\r\nEND:VEVENT\r\n"
|
||||
)
|
||||
|
||||
def test_issue_70(self):
|
||||
"""Issue #70 - e.decode("RRULE") causes Attribute Error
|
||||
https://github.com/collective/icalendar/issues/70
|
||||
"""
|
||||
|
||||
ical_str = """BEGIN:VEVENT
|
||||
CREATED:20081114T072804Z
|
||||
UID:D449CA84-00A3-4E55-83E1-34B58268853B
|
||||
DTEND:20070220T180000
|
||||
RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20070619T225959
|
||||
TRANSP:OPAQUE
|
||||
SUMMARY:Esb mellon phone conf
|
||||
DTSTART:20070220T170000
|
||||
DTSTAMP:20070221T095412Z
|
||||
SEQUENCE:0
|
||||
END:VEVENT"""
|
||||
|
||||
cal = icalendar.Calendar.from_ical(ical_str)
|
||||
recur = cal.decoded("RRULE")
|
||||
self.assertIsInstance(recur, icalendar.vRecur)
|
||||
self.assertEqual(
|
||||
recur.to_ical(),
|
||||
b'FREQ=WEEKLY;UNTIL=20070619T225959;INTERVAL=1'
|
||||
)
|
||||
|
||||
def test_issue_82(self):
|
||||
"""Issue #82 - vBinary __repr__ called rather than to_ical from
|
||||
container types
|
||||
https://github.com/collective/icalendar/issues/82
|
||||
"""
|
||||
|
||||
b = icalendar.vBinary('text')
|
||||
b.params['FMTTYPE'] = 'text/plain'
|
||||
self.assertEqual(b.to_ical(), b'dGV4dA==')
|
||||
e = icalendar.Event()
|
||||
e.add('ATTACH', b)
|
||||
self.assertEqual(
|
||||
e.to_ical(),
|
||||
b"BEGIN:VEVENT\r\nATTACH;ENCODING=BASE64;FMTTYPE=text/plain;"
|
||||
b"VALUE=BINARY:dGV4dA==\r\nEND:VEVENT\r\n"
|
||||
)
|
||||
|
||||
def test_issue_100(self):
|
||||
"""Issue #100 - Transformed doctests into unittests, Test fixes and
|
||||
cleanup.
|
||||
https://github.com/collective/icalendar/pull/100
|
||||
"""
|
||||
|
||||
ical_content = "BEGIN:VEVENT\r\nSUMMARY;LANGUAGE=ru:te\r\nEND:VEVENT"
|
||||
icalendar.Event.from_ical(ical_content).to_ical()
|
||||
|
||||
def test_issue_101(self):
|
||||
"""Issue #101 - icalender is choking on umlauts in ORGANIZER
|
||||
|
||||
https://github.com/collective/icalendar/issues/101
|
||||
"""
|
||||
ical_str = """BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
X-WR-CALNAME:Kalender von acme\, admin
|
||||
PRODID:-//The Horde Project//Horde_iCalendar Library\, Horde 3.3.5//EN
|
||||
METHOD:PUBLISH
|
||||
BEGIN:VEVENT
|
||||
DTSTART:20130416T100000Z
|
||||
DTEND:20130416T110000Z
|
||||
DTSTAMP:20130416T092616Z
|
||||
UID:20130416112341.10064jz0k4j7uem8@acmenet.de
|
||||
CREATED:20130416T092341Z
|
||||
LAST-MODIFIED:20130416T092341Z
|
||||
SUMMARY:wichtiger termin 1
|
||||
ORGANIZER;CN="acme, ädmin":mailto:adm-acme@mydomain.de
|
||||
LOCATION:im büro
|
||||
CLASS:PUBLIC
|
||||
STATUS:CONFIRMED
|
||||
TRANSP:OPAQUE
|
||||
END:VEVENT
|
||||
END:VCALENDAR"""
|
||||
|
||||
cal = icalendar.Calendar.from_ical(ical_str)
|
||||
org_cn = cal.walk('VEVENT')[0]['ORGANIZER'].params['CN']
|
||||
self.assertEqual(org_cn, u'acme, ädmin')
|
||||
|
||||
def test_issue_112(self):
|
||||
"""Issue #112 - No timezone info on EXDATE
|
||||
https://github.com/collective/icalendar/issues/112
|
||||
"""
|
||||
directory = os.path.dirname(__file__)
|
||||
path = os.path.join(directory,
|
||||
'issue_112_missing_tzinfo_on_exdate.ics')
|
||||
with open(path, 'rb') as ics:
|
||||
cal = icalendar.Calendar.from_ical(ics.read())
|
||||
event = cal.walk('VEVENT')[0]
|
||||
|
||||
event_ical = to_unicode(event.to_ical()) # Py3 str type doesn't
|
||||
# support buffer API
|
||||
# General timezone aware dates in ical string
|
||||
self.assertTrue('DTSTART;TZID=America/New_York:20130907T120000'
|
||||
in event_ical)
|
||||
self.assertTrue('DTEND;TZID=America/New_York:20130907T170000'
|
||||
in event_ical)
|
||||
# Specific timezone aware exdates in ical string
|
||||
self.assertTrue('EXDATE;TZID=America/New_York:20131012T120000'
|
||||
in event_ical)
|
||||
self.assertTrue('EXDATE;TZID=America/New_York:20131011T120000'
|
||||
in event_ical)
|
||||
|
||||
self.assertEqual(event['exdate'][0].dts[0].dt.tzname(), 'EDT')
|
||||
|
||||
def test_issue_114(self):
|
||||
"""Issue #114/#115 - invalid line in event breaks the parser
|
||||
https://github.com/collective/icalendar/issues/114
|
||||
"""
|
||||
|
||||
directory = os.path.dirname(__file__)
|
||||
ics = open(os.path.join(directory, 'issue_114_invalid_line.ics'), 'rb')
|
||||
with self.assertRaises(ValueError):
|
||||
cal = icalendar.Calendar.from_ical(ics.read())
|
||||
cal # pep 8
|
||||
ics.close()
|
||||
|
||||
def test_issue_116(self):
|
||||
"""Issue #116/#117 - How to add 'X-APPLE-STRUCTURED-LOCATION'
|
||||
"""
|
||||
event = icalendar.Event()
|
||||
event.add(
|
||||
"X-APPLE-STRUCTURED-LOCATION",
|
||||
"geo:-33.868900,151.207000",
|
||||
parameters={
|
||||
"VALUE": "URI",
|
||||
"X-ADDRESS": "367 George Street Sydney CBD NSW 2000",
|
||||
"X-APPLE-RADIUS": "72",
|
||||
"X-TITLE": "367 George Street"
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
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'
|
||||
)
|
||||
|
||||
# roundtrip
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
icalendar.Event.from_ical(event.to_ical()).to_ical()
|
||||
)
|
253
libs/icalendar-3.6.1/src/icalendar/tests/test_icalendar.py
Normal file
253
libs/icalendar-3.6.1/src/icalendar/tests/test_icalendar.py
Normal file
@ -0,0 +1,253 @@
|
||||
# coding: utf-8
|
||||
from icalendar.tests import unittest
|
||||
|
||||
|
||||
class IcalendarTestCase (unittest.TestCase):
|
||||
|
||||
def test_long_lines(self):
|
||||
from ..parser import Contentlines, Contentline
|
||||
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'
|
||||
)
|
||||
|
||||
# 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', '']
|
||||
)
|
||||
|
||||
def test_contentline_class(self):
|
||||
from ..parser import Contentline, Parameters
|
||||
from ..prop import vText
|
||||
|
||||
self.assertEqual(
|
||||
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()
|
||||
self.assertEqual(
|
||||
c,
|
||||
(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 ')
|
||||
)
|
||||
|
||||
# http://tools.ietf.org/html/rfc5545#section-3.3.11
|
||||
# An intentional formatted text line break MUST only be included in
|
||||
# a "TEXT" property value by representing the line break with the
|
||||
# character sequence of BACKSLASH, followed by a LATIN SMALL LETTER
|
||||
# N or a LATIN CAPITAL LETTER N, that is "\n" or "\N".
|
||||
|
||||
# Newlines are not allwoed in content lines
|
||||
self.assertRaises(AssertionError, Contentline, b'1234\r\n\r\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.')
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
# It can parse itself into parts,
|
||||
# which is a tuple of (name, params, vals)
|
||||
self.assertEqual(
|
||||
Contentline('dtstart:20050101T120000').parts(),
|
||||
('dtstart', Parameters({}), '20050101T120000')
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('dtstart;value=datetime:20050101T120000').parts(),
|
||||
('dtstart', Parameters({'VALUE': 'datetime'}), '20050101T120000')
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
||||
self.assertEqual(
|
||||
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')
|
||||
self.assertEqual(
|
||||
Contentline.from_parts(*parts),
|
||||
'ATTENDEE;CN="Max Rasmussen";ROLE=REQ-PARTICIPANT:'
|
||||
'MAILTO:maxm@example.com'
|
||||
)
|
||||
|
||||
# and again
|
||||
parts = ('ATTENDEE', Parameters(), 'MAILTO:maxm@example.com')
|
||||
self.assertEqual(
|
||||
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'))
|
||||
self.assertEqual(
|
||||
Contentline.from_parts(*parts),
|
||||
'ATTENDEE:MAILTO:test@example.com'
|
||||
)
|
||||
|
||||
# A value in UTF-8
|
||||
parts = ('SUMMARY', Parameters(), vText('INternational char æ ø å'))
|
||||
self.assertEqual(
|
||||
Contentline.from_parts(*parts),
|
||||
u'SUMMARY:INternational char æ ø å'
|
||||
)
|
||||
|
||||
# A value can also be unicode
|
||||
parts = ('SUMMARY', Parameters(), vText(u'INternational char æ ø å'))
|
||||
self.assertEqual(
|
||||
Contentline.from_parts(*parts),
|
||||
u'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(
|
||||
sorted(params.items()),
|
||||
sorted([('ROLE', 'REQ-PARTICIPANT'), ('CN', 'Max Rasmussen')])
|
||||
)
|
||||
|
||||
# And the traditional failure
|
||||
with self.assertRaisesRegexp(
|
||||
ValueError,
|
||||
'Content line could not be parsed into parts'
|
||||
):
|
||||
Contentline('ATTENDEE;maxm@example.com').parts()
|
||||
|
||||
# Another failure:
|
||||
with self.assertRaisesRegexp(
|
||||
ValueError,
|
||||
'Content line could not be parsed into parts'
|
||||
):
|
||||
Contentline(':maxm@example.com').parts()
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('key;param=:value').parts(),
|
||||
('key', Parameters({'PARAM': ''}), 'value')
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('key;param="pvalue":value').parts(),
|
||||
('key', Parameters({'PARAM': 'pvalue'}), 'value')
|
||||
)
|
||||
|
||||
# Should bomb on missing param:
|
||||
with self.assertRaisesRegexp(
|
||||
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')
|
||||
)
|
||||
|
||||
# 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')
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
Contentline('key;param="pValue":value', strict=True).parts(),
|
||||
('key', Parameters({'PARAM': 'pValue'}), 'value')
|
||||
)
|
||||
|
||||
def test_fold_line(self):
|
||||
from ..parser import foldline
|
||||
|
||||
self.assertEqual(foldline(u'foo'), u'foo')
|
||||
self.assertEqual(
|
||||
foldline(u"Lorem ipsum dolor sit amet, consectetur adipiscing "
|
||||
u"elit. Vestibulum convallis imperdiet dui posuere."),
|
||||
(u'Lorem ipsum dolor sit amet, consectetur adipiscing elit. '
|
||||
u'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(u'привет'.encode('utf-8'), limit=3)
|
||||
|
||||
self.assertEqual(foldline(u'foobar', limit=4), u'foo\r\n bar')
|
||||
self.assertEqual(
|
||||
foldline(u'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
|
||||
u'. Vestibulum convallis imperdiet dui posuere.'),
|
||||
(u'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
|
||||
u' Vestibulum conval\r\n lis imperdiet dui posuere.')
|
||||
)
|
||||
self.assertEqual(
|
||||
foldline(u'DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ'),
|
||||
u'DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭ\r\n ЮЯ'
|
||||
)
|
||||
|
||||
def test_value_double_quoting(self):
|
||||
from ..parser import dquote
|
||||
self.assertEqual(dquote('Max'), 'Max')
|
||||
self.assertEqual(dquote('Rasmussen, Max'), '"Rasmussen, Max"')
|
||||
self.assertEqual(dquote('name:value'), '"name:value"')
|
||||
|
||||
def test_q_split(self):
|
||||
from ..parser import q_split
|
||||
self.assertEqual(q_split('Max,Moller,"Rasmussen, Max"'),
|
||||
['Max', 'Moller', '"Rasmussen, Max"'])
|
||||
|
||||
def test_q_join(self):
|
||||
from ..parser import q_join
|
||||
self.assertEqual(q_join(['Max', 'Moller', 'Rasmussen, Max']),
|
||||
'Max,Moller,"Rasmussen, Max"')
|
28
libs/icalendar-3.6.1/src/icalendar/tests/test_multiple.py
Normal file
28
libs/icalendar-3.6.1/src/icalendar/tests/test_multiple.py
Normal file
@ -0,0 +1,28 @@
|
||||
from icalendar import Calendar
|
||||
from icalendar.prop import vText
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import os
|
||||
|
||||
|
||||
class TestMultiple(unittest.TestCase):
|
||||
"""A example with multiple VCALENDAR components"""
|
||||
|
||||
def test_multiple(self):
|
||||
|
||||
directory = os.path.dirname(__file__)
|
||||
cals = Calendar.from_ical(
|
||||
open(os.path.join(directory, 'multiple.ics'), 'rb').read(),
|
||||
multiple=True
|
||||
)
|
||||
|
||||
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')
|
||||
)
|
207
libs/icalendar-3.6.1/src/icalendar/tests/test_property_params.py
Normal file
207
libs/icalendar-3.6.1/src/icalendar/tests/test_property_params.py
Normal file
@ -0,0 +1,207 @@
|
||||
# coding: utf-8
|
||||
from icalendar import Calendar
|
||||
from icalendar import Event
|
||||
from icalendar import Parameters
|
||||
from icalendar import vCalAddress
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import icalendar
|
||||
|
||||
|
||||
class TestPropertyParams(unittest.TestCase):
|
||||
|
||||
def test_property_params(self):
|
||||
# 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.params["CN"] = "Doe, John"
|
||||
ical = Calendar()
|
||||
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"""
|
||||
|
||||
self.assertEqual(ical_str, exp_str)
|
||||
|
||||
# other way around: ensure the property parameters can be restored from
|
||||
# an icalendar string.
|
||||
ical2 = Calendar.from_ical(ical_str)
|
||||
self.assertEqual(ical2.get('ORGANIZER').params.get('CN'), 'Doe, John')
|
||||
|
||||
def test_unicode_param(self):
|
||||
cal_address = vCalAddress('mailto:john.doe@example.org')
|
||||
cal_address.params["CN"] = "Джон Доу"
|
||||
vevent = Event()
|
||||
vevent['ORGANIZER'] = cal_address
|
||||
self.assertEqual(
|
||||
vevent.to_ical().decode('utf-8'),
|
||||
u'BEGIN:VEVENT\r\n'
|
||||
u'ORGANIZER;CN="Джон Доу":mailto:john.doe@example.org\r\n'
|
||||
u'END:VEVENT\r\n'
|
||||
)
|
||||
|
||||
self.assertEqual(vevent['ORGANIZER'].params['CN'],
|
||||
'Джон Доу')
|
||||
|
||||
def test_quoting(self):
|
||||
# not double-quoted
|
||||
self._test_quoting(u"Aramis", u'Aramis')
|
||||
# if a space is present - enclose in double quotes
|
||||
self._test_quoting(u"Aramis Alameda", u'"Aramis Alameda"')
|
||||
# a single quote in parameter value - double quote the value
|
||||
self._test_quoting(u"Aramis d'Alameda", u'"Aramis d\'Alameda"')
|
||||
# double quote is replaced with single quote
|
||||
self._test_quoting(u"Aramis d\"Alameda", u'"Aramis d\'Alameda"')
|
||||
self._test_quoting(u"Арамис д'Аламеда", u'"Арамис д\'Аламеда"')
|
||||
|
||||
def _test_quoting(self, cn_param, cn_quoted):
|
||||
"""
|
||||
@param cn_param: CN parameter value to test for quoting
|
||||
@param cn_quoted: expected quoted parameter in icalendar format
|
||||
"""
|
||||
vevent = Event()
|
||||
attendee = vCalAddress('test@mail.com')
|
||||
attendee.params['CN'] = cn_param
|
||||
vevent.add('ATTENDEE', attendee)
|
||||
self.assertEqual(
|
||||
vevent.to_ical(),
|
||||
b'BEGIN:VEVENT\r\nATTENDEE;CN=' + cn_quoted.encode('utf-8') +
|
||||
b':test@mail.com\r\nEND:VEVENT\r\n'
|
||||
)
|
||||
|
||||
def test_escaping(self):
|
||||
# verify that escaped non safe chars are decoded correctly
|
||||
NON_SAFE_CHARS = u',\\;:'
|
||||
for char in NON_SAFE_CHARS:
|
||||
cn_escaped = u"Society\\%s 2014" % char
|
||||
cn_decoded = u"Society%s 2014" % char
|
||||
vevent = Event.from_ical(
|
||||
u'BEGIN:VEVENT\r\n'
|
||||
u'ORGANIZER;CN=%s:that\r\n'
|
||||
u'END:VEVENT\r\n' % cn_escaped
|
||||
)
|
||||
self.assertEqual(vevent['ORGANIZER'].params['CN'], cn_decoded)
|
||||
|
||||
vevent = Event.from_ical(
|
||||
'BEGIN:VEVENT\r\n'
|
||||
'ORGANIZER;CN=that\\, that\\; %th%%at%\\\\ that\\:'
|
||||
':это\\, то\\; that\\\\ %th%%at%\\:\r\n'
|
||||
'END:VEVENT\r\n'
|
||||
)
|
||||
self.assertEqual(
|
||||
vevent['ORGANIZER'].params['CN'],
|
||||
r'that, that; %th%%at%\ that:'
|
||||
)
|
||||
self.assertEqual(
|
||||
vevent['ORGANIZER'].to_ical().decode('utf-8'),
|
||||
u'это, то; that\\ %th%%at%:'
|
||||
)
|
||||
|
||||
def test_parameters_class(self):
|
||||
|
||||
# Simple parameter:value pair
|
||||
p = Parameters(parameter1='Value1')
|
||||
self.assertEqual(p.to_ical(), b'PARAMETER1=Value1')
|
||||
|
||||
# keys are converted to upper
|
||||
self.assertEqual(list(p.keys()), ['PARAMETER1'])
|
||||
|
||||
# Parameters are case insensitive
|
||||
self.assertEqual(p['parameter1'], 'Value1')
|
||||
self.assertEqual(p['PARAMETER1'], 'Value1')
|
||||
|
||||
# Parameter with list of values must be seperated by comma
|
||||
p = Parameters({'parameter1': ['Value1', 'Value2']})
|
||||
self.assertEqual(p.to_ical(), b'PARAMETER1=Value1,Value2')
|
||||
|
||||
# Multiple parameters must be seperated by a semicolon
|
||||
p = Parameters({'RSVP': 'TRUE', 'ROLE': 'REQ-PARTICIPANT'})
|
||||
self.assertEqual(p.to_ical(), b'ROLE=REQ-PARTICIPANT;RSVP=TRUE')
|
||||
|
||||
# Parameter values containing ',;:' must be double quoted
|
||||
p = Parameters({'ALTREP': 'http://www.wiz.org'})
|
||||
self.assertEqual(p.to_ical(), b'ALTREP="http://www.wiz.org"')
|
||||
|
||||
# list items must be quoted seperately
|
||||
p = Parameters({'MEMBER': ['MAILTO:projectA@host.com',
|
||||
'MAILTO:projectB@host.com']})
|
||||
self.assertEqual(
|
||||
p.to_ical(),
|
||||
b'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"'
|
||||
)
|
||||
|
||||
# Now the whole sheebang
|
||||
p = Parameters({'parameter1': 'Value1',
|
||||
'parameter2': ['Value2', 'Value3'],
|
||||
'ALTREP': ['http://www.wiz.org', 'value4']})
|
||||
self.assertEqual(
|
||||
p.to_ical(),
|
||||
(b'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;'
|
||||
b'PARAMETER2=Value2,Value3')
|
||||
)
|
||||
|
||||
# We can also parse parameter strings
|
||||
self.assertEqual(
|
||||
Parameters.from_ical('PARAMETER1=Value 1;param2=Value 2'),
|
||||
Parameters({'PARAMETER1': 'Value 1', 'PARAM2': 'Value 2'})
|
||||
)
|
||||
|
||||
# Including empty strings
|
||||
self.assertEqual(Parameters.from_ical('param='),
|
||||
Parameters({'PARAM': ''}))
|
||||
|
||||
# We can also parse parameter strings
|
||||
self.assertEqual(
|
||||
Parameters.from_ical(
|
||||
'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"'
|
||||
),
|
||||
Parameters({'MEMBER': ['MAILTO:projectA@host.com',
|
||||
'MAILTO:projectB@host.com']})
|
||||
)
|
||||
|
||||
# We can also parse parameter strings
|
||||
self.assertEqual(
|
||||
Parameters.from_ical('ALTREP="http://www.wiz.org",value4;'
|
||||
'PARAMETER1=Value1;PARAMETER2=Value2,Value3'),
|
||||
Parameters({'PARAMETER1': 'Value1',
|
||||
'ALTREP': ['http://www.wiz.org', 'value4'],
|
||||
'PARAMETER2': ['Value2', 'Value3']})
|
||||
)
|
||||
|
||||
def test_parse_and_access_property_params(self):
|
||||
"""Parse an ics string and access some property parameters then.
|
||||
This is a follow-up of a question recieved per email.
|
||||
|
||||
"""
|
||||
ics = """BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID://RESEARCH IN MOTION//BIS 3.0
|
||||
METHOD:REQUEST
|
||||
BEGIN:VEVENT
|
||||
SEQUENCE:2
|
||||
X-RIM-REVISION:0
|
||||
SUMMARY:Test meeting from BB
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
CLASS:PUBLIC
|
||||
ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN="RembrandXS":MAILTO:rembrand@xs4all.nl
|
||||
ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN="RembrandDX":MAILTO:rembrand@daxlab.com
|
||||
ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN="RembrandSB":MAILTO:rembspam@xs4all.nl
|
||||
UID:XRIMCAL-628059586-522954492-9750559
|
||||
DTSTART;VALUE=DATE:20120814
|
||||
DTEND;VALUE=DATE:20120815
|
||||
DESCRIPTION:Test meeting from BB
|
||||
DTSTAMP:20120813T151458Z
|
||||
ORGANIZER:mailto:rembrand@daxlab.com
|
||||
END:VEVENT
|
||||
END:VCALENDAR"""
|
||||
|
||||
cal = icalendar.Calendar.from_ical(ics)
|
||||
event = cal.walk("VEVENT")[0]
|
||||
event['attendee'][0]
|
||||
self.assertEqual(event['attendee'][0].to_ical(),
|
||||
b'MAILTO:rembrand@xs4all.nl')
|
||||
self.assertEqual(event['attendee'][0].params.to_ical(),
|
||||
b'CN=RembrandXS;PARTSTAT=NEEDS-ACTION;RSVP=TRUE')
|
||||
self.assertEqual(event['attendee'][0].params['cn'], u'RembrandXS')
|
62
libs/icalendar-3.6.1/src/icalendar/tests/test_recurrence.py
Normal file
62
libs/icalendar-3.6.1/src/icalendar/tests/test_recurrence.py
Normal file
@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from icalendar.caselessdict import CaselessDict
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import datetime
|
||||
import icalendar
|
||||
import os
|
||||
import pytz
|
||||
|
||||
|
||||
class TestRecurrence(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
directory = os.path.dirname(__file__)
|
||||
self.cal = icalendar.Calendar.from_ical(
|
||||
open(os.path.join(directory, 'recurrence.ics'), 'rb').read()
|
||||
)
|
||||
|
||||
def test_recurrence_exdates_one_line(self):
|
||||
first_event = self.cal.walk('vevent')[0]
|
||||
|
||||
self.assertIsInstance(first_event, CaselessDict)
|
||||
self.assertEqual(
|
||||
first_event['rrule'], {'COUNT': [100], 'FREQ': ['DAILY']}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
first_event['exdate'].to_ical(),
|
||||
b'19960402T010000Z,19960403T010000Z,19960404T010000Z'
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
first_event['exdate'].dts[0].dt,
|
||||
datetime.datetime(1996, 4, 2, 1, 0, tzinfo=pytz.utc)
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
first_event['exdate'].dts[1].dt,
|
||||
datetime.datetime(1996, 4, 3, 1, 0, tzinfo=pytz.utc)
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
first_event['exdate'].dts[2].dt,
|
||||
datetime.datetime(1996, 4, 4, 1, 0, tzinfo=pytz.utc)
|
||||
)
|
||||
|
||||
def test_recurrence_exdates_multiple_lines(self):
|
||||
event = self.cal.walk('vevent')[1]
|
||||
|
||||
exdate = event['exdate']
|
||||
|
||||
# TODO: DOCUMENT BETTER!
|
||||
# In this case we have multiple EXDATE definitions, one per line.
|
||||
# Icalendar makes a list out of this instead of zipping it into one
|
||||
# vDDDLists object. Actually, this feels correct for me, as it also
|
||||
# allows to define different timezones per exdate line - but client
|
||||
# code has to handle this as list and not blindly expecting to be able
|
||||
# to call event['EXDATE'].to_ical() on it:
|
||||
self.assertEqual(isinstance(exdate, list), True) # multiple EXDATE
|
||||
self.assertEqual(exdate[0].to_ical(), b'20120529T100000')
|
||||
|
||||
# TODO: test for embedded timezone information!
|
29
libs/icalendar-3.6.1/src/icalendar/tests/test_time.py
Normal file
29
libs/icalendar-3.6.1/src/icalendar/tests/test_time.py
Normal file
@ -0,0 +1,29 @@
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import datetime
|
||||
import icalendar
|
||||
import os
|
||||
|
||||
|
||||
class TestTime(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
icalendar.cal.types_factory.types_map['X-SOMETIME'] = 'time'
|
||||
|
||||
def tearDown(self):
|
||||
icalendar.cal.types_factory.types_map.pop('X-SOMETIME')
|
||||
|
||||
def test_create_from_ical(self):
|
||||
directory = os.path.dirname(__file__)
|
||||
ics = open(os.path.join(directory, 'time.ics'), 'rb')
|
||||
cal = icalendar.Calendar.from_ical(ics.read())
|
||||
ics.close()
|
||||
|
||||
self.assertEqual(cal['X-SOMETIME'].dt, datetime.time(17, 20, 10))
|
||||
self.assertEqual(cal['X-SOMETIME'].to_ical(), '172010')
|
||||
|
||||
def test_create_to_ical(self):
|
||||
cal = icalendar.Calendar()
|
||||
cal.add('X-SOMETIME', datetime.time(17, 20, 10))
|
||||
self.assertTrue(b'X-SOMETIME;VALUE=TIME:172010' in
|
||||
cal.to_ical().splitlines())
|
141
libs/icalendar-3.6.1/src/icalendar/tests/test_timezoned.py
Normal file
141
libs/icalendar-3.6.1/src/icalendar/tests/test_timezoned.py
Normal file
@ -0,0 +1,141 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import datetime
|
||||
import dateutil.parser
|
||||
import icalendar
|
||||
import os
|
||||
import pytz
|
||||
|
||||
|
||||
class TestTimezoned(unittest.TestCase):
|
||||
|
||||
def test_create_from_ical(self):
|
||||
directory = os.path.dirname(__file__)
|
||||
cal = icalendar.Calendar.from_ical(
|
||||
open(os.path.join(directory, 'timezoned.ics'), 'rb').read()
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
cal['prodid'].to_ical(),
|
||||
b"-//Plone.org//NONSGML plone.app.event//EN"
|
||||
)
|
||||
|
||||
timezones = cal.walk('VTIMEZONE')
|
||||
self.assertEqual(len(timezones), 1)
|
||||
|
||||
tz = timezones[0]
|
||||
self.assertEqual(tz['tzid'].to_ical(), b"Europe/Vienna")
|
||||
|
||||
std = tz.walk('STANDARD')[0]
|
||||
self.assertEqual(
|
||||
std.decoded('TZOFFSETFROM'),
|
||||
datetime.timedelta(0, 7200)
|
||||
)
|
||||
|
||||
ev1 = cal.walk('VEVENT')[0]
|
||||
self.assertEqual(
|
||||
ev1.decoded('DTSTART'),
|
||||
datetime.datetime(2012, 2, 13, 10, 0, 0,
|
||||
tzinfo=pytz.timezone('Europe/Vienna')))
|
||||
self.assertEqual(
|
||||
ev1.decoded('DTSTAMP'),
|
||||
datetime.datetime(2010, 10, 10, 9, 10, 10, tzinfo=pytz.utc))
|
||||
|
||||
def test_create_to_ical(self):
|
||||
cal = icalendar.Calendar()
|
||||
|
||||
cal.add('prodid', u"-//Plone.org//NONSGML plone.app.event//EN")
|
||||
cal.add('version', u"2.0")
|
||||
cal.add('x-wr-calname', u"test create calendar")
|
||||
cal.add('x-wr-caldesc', u"icalendar tests")
|
||||
cal.add('x-wr-relcalid', u"12345")
|
||||
cal.add('x-wr-timezone', u"Europe/Vienna")
|
||||
|
||||
tzc = icalendar.Timezone()
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
tzc.add_component(tzs)
|
||||
tzc.add_component(tzd)
|
||||
cal.add_component(tzc)
|
||||
|
||||
event = icalendar.Event()
|
||||
tz = pytz.timezone("Europe/Vienna")
|
||||
event.add(
|
||||
'dtstart',
|
||||
datetime.datetime(2012, 2, 13, 10, 00, 00, tzinfo=tz))
|
||||
event.add(
|
||||
'dtend',
|
||||
datetime.datetime(2012, 2, 17, 18, 00, 00, tzinfo=tz))
|
||||
event.add(
|
||||
'dtstamp',
|
||||
datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=tz))
|
||||
event.add(
|
||||
'created',
|
||||
datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=tz))
|
||||
event.add('uid', u'123456')
|
||||
event.add(
|
||||
'last-modified',
|
||||
datetime.datetime(2010, 10, 10, 10, 10, 10, tzinfo=tz))
|
||||
event.add('summary', u'artsprint 2012')
|
||||
# event.add('rrule', u'FREQ=YEARLY;INTERVAL=1;COUNT=10')
|
||||
event.add('description', u'sprinting at the artsprint')
|
||||
event.add('location', u'aka bild, wien')
|
||||
event.add('categories', u'first subject')
|
||||
event.add('categories', u'second subject')
|
||||
event.add('attendee', u'häns')
|
||||
event.add('attendee', u'franz')
|
||||
event.add('attendee', u'sepp')
|
||||
event.add('contact', u'Max Mustermann, 1010 Wien')
|
||||
event.add('url', u'http://plone.org')
|
||||
cal.add_component(event)
|
||||
|
||||
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;VALUE=DATE-TIME:19701025T03"
|
||||
"0000|RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10|RRULE:FREQ=YEARLY;B"
|
||||
"YDAY=-1SU;BYMONTH=3|TZNAME:CET|TZOFFSETFROM:+0200|TZOFFSETTO:+01"
|
||||
"00|END:STANDARD|BEGIN:DAYLIGHT|DTSTART;VALUE=DATE-TIME:19700329T"
|
||||
"020000|TZNAME:CEST|TZOFFSETFROM:+0100|TZOFFSETTO:+0200|END:DAYLI"
|
||||
"GHT|END:VTIMEZONE"
|
||||
self.assertTrue(vtimezone_lines in test_out)
|
||||
|
||||
test_str = "DTSTART;TZID=Europe/Vienna;VALUE=DATE-TIME:20120213T100000"
|
||||
self.assertTrue(test_str in test_out)
|
||||
self.assertTrue("ATTENDEE:sepp" in test_out)
|
||||
|
||||
# ical standard expects DTSTAMP and CREATED in UTC
|
||||
self.assertTrue("DTSTAMP;VALUE=DATE-TIME:20101010T091010Z" in test_out)
|
||||
self.assertTrue("CREATED;VALUE=DATE-TIME:20101010T091010Z" in test_out)
|
||||
|
||||
def test_tzinfo_dateutil(self):
|
||||
# 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')
|
||||
self.assertTrue(date.tzinfo.__module__ == 'dateutil.tz')
|
||||
self.assertTrue(date2.tzinfo.__module__ == 'dateutil.tz')
|
||||
|
||||
# make sure, it's parsed properly and doesn't throw an error
|
||||
self.assertTrue(icalendar.vDDDTypes(date).to_ical()
|
||||
== b'20120830T224100Z')
|
||||
self.assertTrue(icalendar.vDDDTypes(date2).to_ical()
|
||||
== b'20120830T224100')
|
349
libs/icalendar-3.6.1/src/icalendar/tests/test_unit_cal.py
Normal file
349
libs/icalendar-3.6.1/src/icalendar/tests/test_unit_cal.py
Normal file
@ -0,0 +1,349 @@
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import icalendar
|
||||
import pytz
|
||||
|
||||
|
||||
class TestCalComponent(unittest.TestCase):
|
||||
|
||||
def test_cal_Component(self):
|
||||
from icalendar.cal import Component, Calendar, Event
|
||||
from icalendar import prop
|
||||
|
||||
# A component is like a dictionary with extra methods and attributes.
|
||||
c = Component()
|
||||
c.name = 'VCALENDAR'
|
||||
|
||||
# Every key defines a property.A property can consist of either a
|
||||
# single item. This can be set with a single value...
|
||||
c['prodid'] = '-//max m//icalendar.mxm.dk/'
|
||||
self.assertEqual(
|
||||
c,
|
||||
Calendar({'PRODID': '-//max m//icalendar.mxm.dk/'})
|
||||
)
|
||||
|
||||
# or with a list
|
||||
c['ATTENDEE'] = ['Max M', 'Rasmussen']
|
||||
self.assertEqual(
|
||||
c,
|
||||
Calendar({'ATTENDEE': ['Max M', 'Rasmussen'],
|
||||
'PRODID': '-//max m//icalendar.mxm.dk/'})
|
||||
)
|
||||
|
||||
### ADD MULTIPLE VALUES TO A PROPERTY
|
||||
|
||||
# if you use the add method you don't have to considder if a value is
|
||||
# a list or not.
|
||||
c = Component()
|
||||
c.name = 'VEVENT'
|
||||
|
||||
# add multiple values at once
|
||||
c.add('attendee',
|
||||
['test@test.com', 'test2@test.com'])
|
||||
|
||||
# or add one per line
|
||||
c.add('attendee', 'maxm@mxm.dk')
|
||||
c.add('attendee', 'test@example.dk')
|
||||
|
||||
# add again multiple values at once to very concatenaton of lists
|
||||
c.add('attendee',
|
||||
['test3@test.com', 'test4@test.com'])
|
||||
|
||||
self.assertEqual(
|
||||
c,
|
||||
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')
|
||||
]})
|
||||
)
|
||||
|
||||
###
|
||||
|
||||
# You can get the values back directly ...
|
||||
c.add('prodid', '-//my product//')
|
||||
self.assertEqual(c['prodid'], prop.vText(u'-//my product//'))
|
||||
|
||||
# ... or decoded to a python type
|
||||
self.assertEqual(c.decoded('prodid'), b'-//my product//')
|
||||
|
||||
# With default values for non existing properties
|
||||
self.assertEqual(c.decoded('version', 'No Version'), 'No Version')
|
||||
|
||||
c.add('rdate', [datetime(2013, 3, 28), datetime(2013, 3, 27)])
|
||||
self.assertTrue(isinstance(c.decoded('rdate'), prop.vDDDLists))
|
||||
|
||||
# The component can render itself in the RFC 2445 format.
|
||||
c = Component()
|
||||
c.name = 'VCALENDAR'
|
||||
c.add('attendee', 'Max M')
|
||||
self.assertEqual(
|
||||
c.to_ical(),
|
||||
b'BEGIN:VCALENDAR\r\nATTENDEE:Max M\r\nEND:VCALENDAR\r\n'
|
||||
)
|
||||
|
||||
# Components can be nested, so You can add a subcompont. Eg a calendar
|
||||
# holds events.
|
||||
e = Component(summary='A brief history of time')
|
||||
e.name = 'VEVENT'
|
||||
e.add('dtend', '20000102T000000', encode=0)
|
||||
e.add('dtstart', '20000101T000000', encode=0)
|
||||
self.assertEqual(
|
||||
e.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'
|
||||
)
|
||||
|
||||
c.add_component(e)
|
||||
self.assertEqual(
|
||||
c.subcomponents,
|
||||
[Event({'DTEND': '20000102T000000', 'DTSTART': '20000101T000000',
|
||||
'SUMMARY': 'A brief history of time'})]
|
||||
)
|
||||
|
||||
# We can walk over nested componentes with the walk method.
|
||||
self.assertEqual([i.name for i in c.walk()], ['VCALENDAR', 'VEVENT'])
|
||||
|
||||
# We can also just walk over specific component types, by filtering
|
||||
# them on their name.
|
||||
self.assertEqual([i.name for i in c.walk('VEVENT')], ['VEVENT'])
|
||||
|
||||
self.assertEqual(
|
||||
[i['dtstart'] for i in c.walk('VEVENT')],
|
||||
['20000101T000000']
|
||||
)
|
||||
|
||||
# We can enumerate property items recursively with the property_items
|
||||
# method.
|
||||
self.assertEqual(
|
||||
c.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')]
|
||||
)
|
||||
|
||||
# We can also enumerate property items just under the component.
|
||||
self.assertEqual(
|
||||
c.property_items(recursive=False),
|
||||
[('BEGIN', b'VCALENDAR'),
|
||||
('ATTENDEE', prop.vCalAddress('Max M')),
|
||||
('END', b'VCALENDAR')]
|
||||
)
|
||||
|
||||
sc = c.subcomponents[0]
|
||||
self.assertEqual(
|
||||
sc.property_items(recursive=False),
|
||||
[('BEGIN', b'VEVENT'), ('DTEND', '20000102T000000'),
|
||||
('DTSTART', '20000101T000000'),
|
||||
('SUMMARY', 'A brief history of time'), ('END', b'VEVENT')]
|
||||
)
|
||||
|
||||
# Text fields which span multiple mulitple lines require proper
|
||||
# indenting
|
||||
c = Calendar()
|
||||
c['description'] = u'Paragraph one\n\nParagraph two'
|
||||
self.assertEqual(
|
||||
c.to_ical(),
|
||||
b'BEGIN:VCALENDAR\r\nDESCRIPTION:Paragraph one\\n\\nParagraph two'
|
||||
+ b'\r\nEND:VCALENDAR\r\n'
|
||||
)
|
||||
|
||||
# INLINE properties have their values on one property line. Note the
|
||||
# double quoting of the value with a colon in it.
|
||||
c = Calendar()
|
||||
c['resources'] = 'Chair, Table, "Room: 42"'
|
||||
self.assertEqual(
|
||||
c,
|
||||
Calendar({'RESOURCES': 'Chair, Table, "Room: 42"'})
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
c.to_ical(),
|
||||
b'BEGIN:VCALENDAR\r\nRESOURCES:Chair\\, Table\\, "Room: 42"\r\n'
|
||||
+ b'END:VCALENDAR\r\n'
|
||||
)
|
||||
|
||||
# The inline values must be handled by the get_inline() and
|
||||
# set_inline() methods.
|
||||
self.assertEqual(
|
||||
c.get_inline('resources', decode=0),
|
||||
[u'Chair', u'Table', u'Room: 42']
|
||||
)
|
||||
|
||||
# These can also be decoded
|
||||
self.assertEqual(
|
||||
c.get_inline('resources', decode=1),
|
||||
[b'Chair', b'Table', b'Room: 42']
|
||||
)
|
||||
|
||||
# You can set them directly ...
|
||||
c.set_inline('resources', ['A', 'List', 'of', 'some, recources'],
|
||||
encode=1)
|
||||
self.assertEqual(c['resources'], 'A,List,of,"some, recources"')
|
||||
|
||||
# ... and back again
|
||||
self.assertEqual(
|
||||
c.get_inline('resources', decode=0),
|
||||
['A', 'List', 'of', 'some, recources']
|
||||
)
|
||||
|
||||
c['freebusy'] = '19970308T160000Z/PT3H,19970308T200000Z/PT1H,'\
|
||||
+ '19970308T230000Z/19970309T000000Z'
|
||||
self.assertEqual(
|
||||
c.get_inline('freebusy', decode=0),
|
||||
['19970308T160000Z/PT3H', '19970308T200000Z/PT1H',
|
||||
'19970308T230000Z/19970309T000000Z']
|
||||
)
|
||||
|
||||
freebusy = c.get_inline('freebusy', decode=1)
|
||||
self.assertTrue(isinstance(freebusy[0][0], datetime))
|
||||
self.assertTrue(isinstance(freebusy[0][1], timedelta))
|
||||
|
||||
def test_cal_Component_add(self):
|
||||
# Test the for timezone correctness: dtstart should preserve it's
|
||||
# timezone, crated, dtstamp and last-modified must be in UTC.
|
||||
Component = icalendar.cal.Component
|
||||
comp = Component()
|
||||
comp.add('dtstart', datetime(2010, 10, 10, 10, 0, 0,
|
||||
tzinfo=pytz.timezone("Europe/Vienna")))
|
||||
comp.add('created', datetime(2010, 10, 10, 12, 0, 0))
|
||||
comp.add('dtstamp', datetime(2010, 10, 10, 14, 0, 0,
|
||||
tzinfo=pytz.timezone("Europe/Vienna")))
|
||||
comp.add('last-modified', datetime(2010, 10, 10, 16, 0, 0,
|
||||
tzinfo=pytz.utc))
|
||||
|
||||
lines = comp.to_ical().splitlines()
|
||||
self.assertTrue(
|
||||
b"DTSTART;TZID=Europe/Vienna;VALUE=DATE-TIME:20101010T100000"
|
||||
in lines)
|
||||
self.assertTrue(b"CREATED;VALUE=DATE-TIME:20101010T120000Z" in lines)
|
||||
self.assertTrue(b"DTSTAMP;VALUE=DATE-TIME:20101010T130000Z" in lines)
|
||||
self.assertTrue(
|
||||
b"LAST-MODIFIED;VALUE=DATE-TIME:20101010T160000Z" in lines
|
||||
)
|
||||
|
||||
def test_cal_Component_add_no_reencode(self):
|
||||
"""Already encoded values should not be re-encoded.
|
||||
"""
|
||||
from icalendar import cal, prop
|
||||
comp = cal.Component()
|
||||
comp.add('ATTACH', 'me')
|
||||
|
||||
comp.add('ATTACH', 'you', encode=False)
|
||||
binary = prop.vBinary('us')
|
||||
comp.add('ATTACH', binary)
|
||||
|
||||
self.assertEqual(comp['ATTACH'], [u'me', 'you', binary])
|
||||
|
||||
def test_cal_Component_add_property_parameter(self):
|
||||
# Test the for timezone correctness: dtstart should preserve it's
|
||||
# timezone, crated, dtstamp and last-modified must be in UTC.
|
||||
Component = icalendar.cal.Component
|
||||
comp = Component()
|
||||
comp.add('X-TEST-PROP', 'tryout.',
|
||||
parameters={'prop1': 'val1', 'prop2': 'val2'})
|
||||
lines = comp.to_ical().splitlines()
|
||||
self.assertTrue(b"X-TEST-PROP;PROP1=val1;PROP2=val2:tryout." in lines)
|
||||
|
||||
def test_cal_Component_from_ical(self):
|
||||
# Check for proper handling of TZID parameter of datetime properties
|
||||
Component = icalendar.cal.Component
|
||||
for component_name, property_name in (
|
||||
('VEVENT', 'DTSTART'),
|
||||
('VEVENT', 'DTEND'),
|
||||
('VEVENT', 'RECURRENCE-ID'),
|
||||
('VTODO', 'DUE')
|
||||
):
|
||||
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)
|
||||
self.assertEqual(str(component[property_name].dt.tzinfo.zone),
|
||||
"America/Denver")
|
||||
|
||||
component_str = 'BEGIN:' + component_name + '\n'
|
||||
component_str += property_name + ':'
|
||||
component_str += '20120404T073000\nEND:' + component_name
|
||||
component = Component.from_ical(component_str)
|
||||
self.assertEqual(component[property_name].dt.tzinfo,
|
||||
None)
|
||||
|
||||
|
||||
class TestCal(unittest.TestCase):
|
||||
|
||||
def test_cal_ComponentFactory(self):
|
||||
ComponentFactory = icalendar.cal.ComponentFactory
|
||||
factory = ComponentFactory()
|
||||
component = factory['VEVENT']
|
||||
event = component(dtstart='19700101')
|
||||
self.assertEqual(
|
||||
event.to_ical(),
|
||||
b'BEGIN:VEVENT\r\nDTSTART:19700101\r\nEND:VEVENT\r\n'
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
factory.get('VCALENDAR', icalendar.cal.Component),
|
||||
icalendar.cal.Calendar)
|
||||
|
||||
def test_cal_Calendar(self):
|
||||
# Setting up a minimal calendar component looks like this
|
||||
cal = icalendar.cal.Calendar()
|
||||
|
||||
# Some properties are required to be compliant
|
||||
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 = icalendar.cal.Event()
|
||||
event['summary'] = 'Python meeting about calendaring'
|
||||
event['uid'] = '42'
|
||||
event.add('dtstart', datetime(2005, 4, 4, 8, 0, 0))
|
||||
cal.add_component(event)
|
||||
self.assertEqual(
|
||||
cal.subcomponents[0].to_ical(),
|
||||
b'BEGIN:VEVENT\r\nSUMMARY:Python meeting about calendaring\r\n'
|
||||
+ b'DTSTART;VALUE=DATE-TIME:20050404T080000\r\nUID:42\r\n'
|
||||
+ b'END:VEVENT\r\n')
|
||||
|
||||
# Write to disc
|
||||
import tempfile
|
||||
import os
|
||||
directory = tempfile.mkdtemp()
|
||||
open(os.path.join(directory, 'test.ics'), 'wb').write(cal.to_ical())
|
||||
|
||||
# Parsing a complete calendar from a string will silently ignore bogus
|
||||
# events. The bogosity in the following is the third EXDATE: it has an
|
||||
# empty DATE.
|
||||
s = '\r\n'.join(('BEGIN:VCALENDAR',
|
||||
'PRODID:-//Google Inc//Google Calendar 70.9054//EN',
|
||||
'VERSION:2.0',
|
||||
'CALSCALE:GREGORIAN',
|
||||
'METHOD:PUBLISH',
|
||||
'BEGIN:VEVENT',
|
||||
'DESCRIPTION:Perfectly OK event',
|
||||
'DTSTART;VALUE=DATE:20080303',
|
||||
'DTEND;VALUE=DATE:20080304',
|
||||
'RRULE:FREQ=DAILY;UNTIL=20080323T235959Z',
|
||||
'EXDATE;VALUE=DATE:20080311',
|
||||
'END:VEVENT',
|
||||
'BEGIN:VEVENT',
|
||||
'DESCRIPTION:Bogus event',
|
||||
'DTSTART;VALUE=DATE:20080303',
|
||||
'DTEND;VALUE=DATE:20080304',
|
||||
'RRULE:FREQ=DAILY;UNTIL=20080323T235959Z',
|
||||
'EXDATE;VALUE=DATE:20080311',
|
||||
'EXDATE;VALUE=DATE:',
|
||||
'END:VEVENT',
|
||||
'END:VCALENDAR'))
|
||||
self.assertEqual(
|
||||
[e['DESCRIPTION'].to_ical()
|
||||
for e in icalendar.cal.Calendar.from_ical(s).walk('VEVENT')],
|
||||
[b'Perfectly OK event'])
|
@ -0,0 +1,90 @@
|
||||
from icalendar.tests import unittest
|
||||
|
||||
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']
|
||||
|
||||
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 = 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 = dict(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)]
|
||||
)
|
||||
|
||||
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)]
|
||||
)
|
||||
|
||||
def test_CaselessDict(self):
|
||||
CaselessDict = icalendar.caselessdict.CaselessDict
|
||||
|
||||
ncd = CaselessDict(key1='val1', key2='val2')
|
||||
self.assertEqual(
|
||||
ncd,
|
||||
CaselessDict({'KEY2': 'val2', 'KEY1': 'val1'})
|
||||
)
|
||||
|
||||
self.assertEqual(ncd['key1'], 'val1')
|
||||
self.assertEqual(ncd['KEY1'], 'val1')
|
||||
|
||||
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)
|
||||
|
||||
del ncd['key4']
|
||||
self.assertFalse('key4' in ncd)
|
||||
|
||||
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'])
|
@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from icalendar.parser_tools import data_encode
|
||||
from icalendar.parser_tools import to_unicode
|
||||
from icalendar.tests import unittest
|
||||
|
||||
|
||||
class TestParserTools(unittest.TestCase):
|
||||
|
||||
def test_parser_tools_to_unicode(self):
|
||||
|
||||
self.assertEqual(to_unicode('spam'), u'spam')
|
||||
self.assertEqual(to_unicode(u'spam'), u'spam')
|
||||
self.assertEqual(to_unicode(u'spam'.encode('utf-8')), u'spam')
|
||||
self.assertEqual(to_unicode(b'\xc6\xb5'), u'\u01b5')
|
||||
self.assertEqual(to_unicode(u'\xc6\xb5'.encode('iso-8859-1')),
|
||||
u'\u01b5')
|
||||
self.assertEqual(to_unicode(b'\xc6\xb5', encoding='ascii'), u'\u01b5')
|
||||
self.assertEqual(to_unicode(1), 1)
|
||||
self.assertEqual(to_unicode(None), None)
|
||||
|
||||
def test_parser_tools_data_encode(self):
|
||||
|
||||
data1 = {
|
||||
u'k1': u'v1', 'k2': 'v2', u'k3': u'v3',
|
||||
'li1': ['it1', u'it2', {'k4': u'v4', u'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]}
|
||||
self.assertEqual(data_encode(data1), res)
|
497
libs/icalendar-3.6.1/src/icalendar/tests/test_unit_prop.py
Normal file
497
libs/icalendar-3.6.1/src/icalendar/tests/test_unit_prop.py
Normal file
@ -0,0 +1,497 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
from datetime import time
|
||||
from datetime import timedelta
|
||||
from icalendar.parser import Parameters
|
||||
from icalendar.tests import unittest
|
||||
|
||||
import pytz
|
||||
|
||||
|
||||
class TestProp(unittest.TestCase):
|
||||
|
||||
def test_prop_vBinary(self):
|
||||
from ..prop import vBinary
|
||||
|
||||
txt = b'This is gibberish'
|
||||
txt_ical = b'VGhpcyBpcyBnaWJiZXJpc2g='
|
||||
self.assertEqual(vBinary(txt).to_ical(), txt_ical)
|
||||
self.assertEqual(vBinary.from_ical(txt_ical), txt)
|
||||
|
||||
# The roundtrip test
|
||||
txt = b'Binary data \x13 \x56'
|
||||
txt_ical = b'QmluYXJ5IGRhdGEgEyBW'
|
||||
self.assertEqual(vBinary(txt).to_ical(), txt_ical)
|
||||
self.assertEqual(vBinary.from_ical(txt_ical), txt)
|
||||
|
||||
self.assertIsInstance(vBinary('txt').params, Parameters)
|
||||
self.assertEqual(
|
||||
vBinary('txt').params, {'VALUE': 'BINARY', 'ENCODING': 'BASE64'}
|
||||
)
|
||||
|
||||
# Long data should not have line breaks, as that would interfere
|
||||
txt = b'a' * 99
|
||||
txt_ical = b'YWFh' * 33
|
||||
self.assertEqual(vBinary(txt).to_ical(), txt_ical)
|
||||
self.assertEqual(vBinary.from_ical(txt_ical), txt)
|
||||
|
||||
def test_prop_vBoolean(self):
|
||||
from ..prop import vBoolean
|
||||
|
||||
self.assertEqual(vBoolean(True).to_ical(), b'TRUE')
|
||||
self.assertEqual(vBoolean(0).to_ical(), b'FALSE')
|
||||
|
||||
# The roundtrip test
|
||||
self.assertEqual(vBoolean.from_ical(vBoolean(True).to_ical()), True)
|
||||
self.assertEqual(vBoolean.from_ical('true'), True)
|
||||
|
||||
def test_prop_vCalAddress(self):
|
||||
from ..prop import vCalAddress
|
||||
txt = b'MAILTO:maxm@mxm.dk'
|
||||
a = vCalAddress(txt)
|
||||
a.params['cn'] = 'Max M'
|
||||
|
||||
self.assertEqual(a.to_ical(), txt)
|
||||
self.assertIsInstance(a.params, Parameters)
|
||||
self.assertEqual(a.params, {'CN': 'Max M'})
|
||||
self.assertEqual(vCalAddress.from_ical(txt), 'MAILTO:maxm@mxm.dk')
|
||||
|
||||
def test_prop_vFloat(self):
|
||||
from ..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')
|
||||
|
||||
def test_prop_vInt(self):
|
||||
from ..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')
|
||||
|
||||
def test_prop_vDDDLists(self):
|
||||
from ..prop import vDDDLists
|
||||
|
||||
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')
|
||||
|
||||
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')
|
||||
|
||||
dt_list = vDDDLists([])
|
||||
self.assertEqual(dt_list.to_ical(), b'')
|
||||
|
||||
dt_list = vDDDLists([datetime(2000, 1, 1)])
|
||||
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')
|
||||
|
||||
def test_prop_vDDDTypes(self):
|
||||
from ..prop import vDDDTypes
|
||||
|
||||
self.assertTrue(isinstance(vDDDTypes.from_ical('20010101T123000'),
|
||||
datetime))
|
||||
|
||||
self.assertEqual(vDDDTypes.from_ical('20010101T123000Z'),
|
||||
datetime(2001, 1, 1, 12, 30, tzinfo=pytz.utc))
|
||||
|
||||
self.assertTrue(isinstance(vDDDTypes.from_ical('20010101'), date))
|
||||
|
||||
self.assertEqual(vDDDTypes.from_ical('P31D'), timedelta(31))
|
||||
|
||||
self.assertEqual(vDDDTypes.from_ical('-P31D'), timedelta(-31))
|
||||
|
||||
# Bad input
|
||||
self.assertRaises(ValueError, vDDDTypes, 42)
|
||||
|
||||
def test_prop_vDate(self):
|
||||
from ..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.from_ical('20010102'), date(2001, 1, 2))
|
||||
|
||||
self.assertRaises(ValueError, vDate, 'd')
|
||||
|
||||
def test_prop_vDatetime(self):
|
||||
from ..prop import vDatetime
|
||||
|
||||
dt = datetime(2001, 1, 1, 12, 30, 0)
|
||||
self.assertEqual(vDatetime(dt).to_ical(), b'20010101T123000')
|
||||
|
||||
self.assertEqual(vDatetime.from_ical('20000101T120000'),
|
||||
datetime(2000, 1, 1, 12, 0))
|
||||
|
||||
dutc = datetime(2001, 1, 1, 12, 30, 0, tzinfo=pytz.utc)
|
||||
self.assertEqual(vDatetime(dutc).to_ical(), b'20010101T123000Z')
|
||||
|
||||
dutc = datetime(1899, 1, 1, 12, 30, 0, tzinfo=pytz.utc)
|
||||
self.assertEqual(vDatetime(dutc).to_ical(), b'18990101T123000Z')
|
||||
|
||||
self.assertEqual(vDatetime.from_ical('20010101T000000'),
|
||||
datetime(2001, 1, 1, 0, 0))
|
||||
|
||||
self.assertRaises(ValueError, vDatetime.from_ical, '20010101T000000A')
|
||||
|
||||
utc = vDatetime.from_ical('20010101T000000Z')
|
||||
self.assertEqual(vDatetime(utc).to_ical(), b'20010101T000000Z')
|
||||
|
||||
# 1 minute before transition to DST
|
||||
dat = vDatetime.from_ical('20120311T015959', 'America/Denver')
|
||||
self.assertEqual(dat.strftime('%Y%m%d%H%M%S %z'),
|
||||
'20120311015959 -0700')
|
||||
|
||||
# After transition to DST
|
||||
dat = vDatetime.from_ical('20120311T030000', 'America/Denver')
|
||||
self.assertEqual(dat.strftime('%Y%m%d%H%M%S %z'),
|
||||
'20120311030000 -0600')
|
||||
|
||||
dat = vDatetime.from_ical('20101010T000000', 'Europe/Vienna')
|
||||
self.assertEqual(vDatetime(dat).to_ical(), b'20101010T000000')
|
||||
|
||||
def test_prop_vDuration(self):
|
||||
from ..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')
|
||||
|
||||
# How does the parsing work?
|
||||
self.assertEqual(vDuration.from_ical('PT1H0M22S'), timedelta(0, 3622))
|
||||
|
||||
self.assertRaises(ValueError, vDuration.from_ical, 'kox')
|
||||
|
||||
self.assertEqual(vDuration.from_ical('-P14D'), timedelta(-14))
|
||||
|
||||
self.assertRaises(ValueError, vDuration, 11)
|
||||
|
||||
def test_prop_vPeriod(self):
|
||||
from ..prop import vPeriod
|
||||
|
||||
# One day in exact datetimes
|
||||
per = (datetime(2000, 1, 1), datetime(2000, 1, 2))
|
||||
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')
|
||||
|
||||
# Roundtrip
|
||||
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)))
|
||||
|
||||
# Roundtrip with absolute time
|
||||
p = vPeriod.from_ical('20000101T000000Z/20000102T000000Z')
|
||||
self.assertEqual(vPeriod(p).to_ical(),
|
||||
b'20000101T000000Z/20000102T000000Z')
|
||||
|
||||
# And an error
|
||||
self.assertRaises(ValueError,
|
||||
vPeriod.from_ical, '20000101T000000/Psd31D')
|
||||
|
||||
# Timezoned
|
||||
dk = pytz.timezone('Europe/Copenhagen')
|
||||
start = datetime(2000, 1, 1, tzinfo=dk)
|
||||
end = datetime(2000, 1, 2, tzinfo=dk)
|
||||
per = (start, end)
|
||||
self.assertEqual(vPeriod(per).to_ical(),
|
||||
b'20000101T000000/20000102T000000')
|
||||
self.assertEqual(vPeriod(per).params['TZID'],
|
||||
'Europe/Copenhagen')
|
||||
|
||||
p = vPeriod((datetime(2000, 1, 1, tzinfo=dk), timedelta(days=31)))
|
||||
self.assertEqual(p.to_ical(), b'20000101T000000/P31D')
|
||||
|
||||
def test_prop_vWeekday(self):
|
||||
from ..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')
|
||||
|
||||
def test_prop_vFrequency(self):
|
||||
from ..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')
|
||||
|
||||
def test_prop_vRecur(self):
|
||||
from ..prop import vRecur
|
||||
|
||||
# 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
|
||||
})
|
||||
self.assertEqual(
|
||||
vRecur(r).to_ical(),
|
||||
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,
|
||||
})
|
||||
self.assertEqual(
|
||||
r.to_ical(),
|
||||
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', 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=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]}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
vRecur(r).to_ical(),
|
||||
b'FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=-SU;'
|
||||
b'BYMONTH=1'
|
||||
)
|
||||
|
||||
# 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')
|
||||
|
||||
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'
|
||||
)
|
||||
|
||||
# and some errors
|
||||
self.assertRaises(ValueError, vRecur.from_ical, 'BYDAY=12')
|
||||
|
||||
def test_prop_vText(self):
|
||||
from ..prop import vText
|
||||
|
||||
self.assertEqual(vText(u'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')
|
||||
|
||||
# Escaped newlines
|
||||
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 2445 support.
|
||||
t = vText(u'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'),
|
||||
u'Text ; with escaped, chars')
|
||||
|
||||
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'), u'Ol\ufffd')
|
||||
|
||||
# Notice how accented E character, encoded with latin-1, got replaced
|
||||
# with the official U+FFFD REPLACEMENT CHARACTER.
|
||||
|
||||
def test_prop_vTime(self):
|
||||
from ..prop import vTime
|
||||
|
||||
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')
|
||||
|
||||
def test_prop_vUri(self):
|
||||
from ..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/')
|
||||
|
||||
def test_prop_vGeo(self):
|
||||
from ..prop import vGeo
|
||||
|
||||
# Pass a list
|
||||
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')
|
||||
|
||||
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.assertRaises(ValueError, vGeo, 'g')
|
||||
|
||||
def test_prop_vUTCOffset(self):
|
||||
from ..prop import vUTCOffset
|
||||
|
||||
self.assertEqual(vUTCOffset(timedelta(hours=2)).to_ical(), '+0200')
|
||||
|
||||
self.assertEqual(vUTCOffset(timedelta(hours=-5)).to_ical(), '-0500')
|
||||
|
||||
self.assertEqual(vUTCOffset(timedelta()).to_ical(), '+0000')
|
||||
|
||||
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=1, minutes=30)).to_ical(),
|
||||
'+0130')
|
||||
|
||||
# Support seconds
|
||||
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(vUTCOffset.from_ical('+0230')).to_ical(),
|
||||
'+0230')
|
||||
|
||||
# And a few failures
|
||||
self.assertRaises(ValueError, vUTCOffset.from_ical, '+323k')
|
||||
|
||||
self.assertRaises(ValueError, vUTCOffset.from_ical, '+2400')
|
||||
|
||||
def test_prop_vInline(self):
|
||||
from ..prop import vInline
|
||||
|
||||
self.assertEqual(vInline('Some text'), 'Some text')
|
||||
self.assertEqual(vInline.from_ical('Some text'), 'Some text')
|
||||
|
||||
t2 = vInline('other text')
|
||||
t2.params['cn'] = 'Test Osterone'
|
||||
self.assertIsInstance(t2.params, Parameters)
|
||||
self.assertEqual(t2.params, {'CN': 'Test Osterone'})
|
||||
|
||||
def test_prop_TypesFactory(self):
|
||||
from ..prop import TypesFactory
|
||||
|
||||
# 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')
|
||||
|
||||
# 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))
|
||||
|
||||
# It can also be used to directly encode property and parameter values
|
||||
self.assertEqual(
|
||||
factory.to_ical('comment', u'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', u'Rasmussen, Max M\xfcller'),
|
||||
b'Rasmussen\\, Max M\xc3\xbcller')
|
||||
self.assertEqual(
|
||||
factory.from_ical('cn', b'Rasmussen\\, Max M\xc3\xb8ller'),
|
||||
u'Rasmussen, Max M\xf8ller'
|
||||
)
|
||||
|
||||
|
||||
class TestPropertyValues(unittest.TestCase):
|
||||
|
||||
def test_vDDDLists_timezone(self):
|
||||
"""Test vDDDLists with timezone information.
|
||||
"""
|
||||
from .. import Event
|
||||
vevent = Event()
|
||||
at = pytz.timezone('Europe/Vienna')
|
||||
dt1 = at.localize(datetime(2013, 1, 1))
|
||||
dt2 = at.localize(datetime(2013, 1, 2))
|
||||
dt3 = at.localize(datetime(2013, 1, 3))
|
||||
vevent.add('rdate', [dt1, dt2])
|
||||
vevent.add('exdate', dt3)
|
||||
ical = vevent.to_ical()
|
||||
|
||||
self.assertTrue(
|
||||
b'RDATE;TZID=Europe/Vienna:20130101T000000,20130102T000000' in ical
|
||||
)
|
||||
self.assertTrue(b'EXDATE;TZID=Europe/Vienna:20130103T000000' in ical)
|
28
libs/icalendar-3.6.1/src/icalendar/tests/test_unit_tools.py
Normal file
28
libs/icalendar-3.6.1/src/icalendar/tests/test_unit_tools.py
Normal file
@ -0,0 +1,28 @@
|
||||
from icalendar.tests import unittest
|
||||
from icalendar.tools import UIDGenerator
|
||||
|
||||
|
||||
class TestTools(unittest.TestCase):
|
||||
|
||||
def test_tools_UIDGenerator(self):
|
||||
|
||||
# Automatic semi-random uid
|
||||
g = UIDGenerator()
|
||||
uid = g.uid()
|
||||
|
||||
txt = uid.to_ical()
|
||||
length = 15 + 1 + 16 + 1 + 11
|
||||
self.assertTrue(len(txt) == length)
|
||||
self.assertTrue(b'@example.com' in txt)
|
||||
|
||||
# You should at least insert your own hostname to be more compliant
|
||||
uid = g.uid('Example.ORG')
|
||||
txt = uid.to_ical()
|
||||
self.assertTrue(len(txt) == length)
|
||||
self.assertTrue(b'@Example.ORG' in txt)
|
||||
|
||||
# You can also insert a path or similar
|
||||
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)
|
3
libs/icalendar-3.6.1/src/icalendar/tests/time.ics
Normal file
3
libs/icalendar-3.6.1/src/icalendar/tests/time.ics
Normal file
@ -0,0 +1,3 @@
|
||||
BEGIN:VCALENDAR
|
||||
X-SOMETIME;VALUE=TIME:172010
|
||||
END:VCALENDAR
|
36
libs/icalendar-3.6.1/src/icalendar/tests/timezoned.ics
Normal file
36
libs/icalendar-3.6.1/src/icalendar/tests/timezoned.ics
Normal file
@ -0,0 +1,36 @@
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Plone.org//NONSGML plone.app.event//EN
|
||||
VERSION:2.0
|
||||
X-WR-CALNAME:test create calendar
|
||||
X-WR-CALDESC:icalendar test
|
||||
X-WR-RELCALID:12345
|
||||
X-WR-TIMEZONE:Europe/Vienna
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/Vienna
|
||||
X-LIC-LOCATION:Europe/Vienna
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0200
|
||||
TZNAME:CEST
|
||||
DTSTART:19700329T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0200
|
||||
TZOFFSETTO:+0100
|
||||
TZNAME:CET
|
||||
DTSTART:19701025T030000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
DTSTART;TZID=Europe/Vienna:20120213T100000
|
||||
DTEND;TZID=Europe/Vienna:20120217T180000
|
||||
DTSTAMP:20101010T091010Z
|
||||
CREATED:20101010T091010Z
|
||||
UID:123456
|
||||
SUMMARY:artsprint 2012
|
||||
DESCRIPTION:sprinting at the artsprint
|
||||
LOCATION:aka bild, wien
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
33
libs/icalendar-3.6.1/src/icalendar/tools.py
Normal file
33
libs/icalendar-3.6.1/src/icalendar/tools.py
Normal file
@ -0,0 +1,33 @@
|
||||
from datetime import datetime
|
||||
from icalendar.parser_tools import to_unicode
|
||||
from icalendar.prop import vDatetime
|
||||
from icalendar.prop import vText
|
||||
from string import ascii_letters
|
||||
from string import digits
|
||||
|
||||
import random
|
||||
|
||||
|
||||
class UIDGenerator(object):
|
||||
"""If you are too lazy to create real uid's.
|
||||
|
||||
"""
|
||||
chars = list(ascii_letters + digits)
|
||||
|
||||
def rnd_string(self, length=16):
|
||||
"""Generates a string with random characters of length.
|
||||
"""
|
||||
return ''.join([random.choice(self.chars) for _ in range(length)])
|
||||
|
||||
def uid(self, host_name='example.com', unique=''):
|
||||
"""Generates a unique id consisting of:
|
||||
datetime-uniquevalue@host.
|
||||
Like:
|
||||
20050105T225746Z-HKtJMqUgdO0jDUwm@example.com
|
||||
"""
|
||||
host_name = to_unicode(host_name)
|
||||
unique = unique or self.rnd_string()
|
||||
today = to_unicode(vDatetime(datetime.today()).to_ical())
|
||||
return vText('%s-%s@%s' % (today,
|
||||
unique,
|
||||
host_name))
|
23
libs/icalendar-3.6.1/tox.ini
Normal file
23
libs/icalendar-3.6.1/tox.ini
Normal file
@ -0,0 +1,23 @@
|
||||
[tox]
|
||||
envlist = py26,py27,py33
|
||||
|
||||
[testenv:py26]
|
||||
deps =
|
||||
unittest2
|
||||
discover
|
||||
coverage
|
||||
commands =
|
||||
coverage erase
|
||||
coverage run --source=icalendar --omit=*tests* {envbindir}/discover icalendar
|
||||
coverage report --omit=*tests*
|
||||
coverage html --omit=*tests*
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
discover
|
||||
coverage
|
||||
commands =
|
||||
coverage erase
|
||||
coverage run --source=icalendar --omit=*tests* {envbindir}/discover icalendar
|
||||
coverage report --omit=*tests*
|
||||
coverage html --omit=*tests*
|
Loading…
x
Reference in New Issue
Block a user