pax_global_header00006660000000000000000000000064122647572110014521gustar00rootroot0000000000000052 comment=c06a6f40ddf75b10bef108d266f58eea1cff4102 icalendar-3.6.1/000077500000000000000000000000001226475721100134525ustar00rootroot00000000000000icalendar-3.6.1/.gitignore000066400000000000000000000002241226475721100154400ustar00rootroot00000000000000*.mo *.py? *.sw? .* bin/ build/ coverage-* develop-eggs/ dist/ docs/_build/ eggs/ htmlcov/ include/ lib/ parts/ src/icalendar.egg-info/ !.gitignore icalendar-3.6.1/.travis.yml000066400000000000000000000003701226475721100155630ustar00rootroot00000000000000language: python python: # - "2.4" - "2.5" - "2.6" - "2.7" # - "3.2" install: - pip install . --use-mirrors - pip install unittest2 --use-mirrors script: - $HOME/virtualenv/python$TRAVIS_PYTHON_VERSION/bin/unit2 discover icalendar [] icalendar-3.6.1/CHANGES.rst000066400000000000000000000252031226475721100152560ustar00rootroot00000000000000 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 ) - 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. icalendar-3.6.1/CONTRIBUTING.rst000066400000000000000000000010531226475721100161120ustar00rootroot00000000000000You 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 icalendar-3.6.1/LICENSE.rst000066400000000000000000000024571226475721100152760ustar00rootroot00000000000000 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. icalendar-3.6.1/MANIFEST.in000066400000000000000000000001511226475721100152050ustar00rootroot00000000000000include *.rst tox.ini graft docs recursive-include src/icalendar * recursive-exclude src/icalendar *.pyc icalendar-3.6.1/README.rst000066400000000000000000000072101226475721100151410ustar00rootroot00000000000000========================================================== 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% icalendar-3.6.1/TODO.rst000066400000000000000000000020011226475721100147420ustar00rootroot00000000000000TODO ==== - 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' icalendar-3.6.1/bootstrap.py000066400000000000000000000074751226475721100160560ustar00rootroot00000000000000############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' # We decided to always use distribute, make sure this is the default for us # USE_DISTRIBUTE = options.distribute USE_DISTRIBUTE = True args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) icalendar-3.6.1/buildout.cfg000066400000000000000000000007001226475721100157570ustar00rootroot00000000000000[buildout] develop = . parts += test py coverage [test] recipe = zc.recipe.testrunner eggs = icalendar[test] defaults = ['--auto-color', '--auto-progress'] [py] recipe = zc.recipe.egg interpreter = py eggs = ${test:eggs} scripts = [coverage] recipe = collective.recipe.template input = inline: #!/bin/sh ./bin/test --coverage ../../coverage -v --auto-progress "$@" output = ${buildout:directory}/bin/coverage mode = 755 icalendar-3.6.1/docs/000077500000000000000000000000001226475721100144025ustar00rootroot00000000000000icalendar-3.6.1/docs/Makefile000066400000000000000000000107721226475721100160510ustar00rootroot00000000000000# 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 ' where 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." icalendar-3.6.1/docs/_themes/000077500000000000000000000000001226475721100160265ustar00rootroot00000000000000icalendar-3.6.1/docs/_themes/icalendar/000077500000000000000000000000001226475721100177505ustar00rootroot00000000000000icalendar-3.6.1/docs/_themes/icalendar/layout.html000066400000000000000000000005771226475721100221640ustar00rootroot00000000000000{# 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 %} icalendar-3.6.1/docs/_themes/icalendar/static/000077500000000000000000000000001226475721100212375ustar00rootroot00000000000000icalendar-3.6.1/docs/_themes/icalendar/static/icalendar.css000066400000000000000000000165231226475721100237020ustar00rootroot00000000000000/* * 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; } icalendar-3.6.1/docs/_themes/icalendar/theme.conf000066400000000000000000000001151226475721100217160ustar00rootroot00000000000000[theme] inherit = basic stylesheet = icalendar.css pygments_style = friendly icalendar-3.6.1/docs/about.rst000066400000000000000000000011241226475721100162440ustar00rootroot00000000000000About ===== `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 icalendar-3.6.1/docs/changelog.rst000066400000000000000000000000341226475721100170600ustar00rootroot00000000000000.. include:: ../CHANGES.rst icalendar-3.6.1/docs/conf.py000066400000000000000000000156731226475721100157150ustar00rootroot00000000000000# -*- 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 # " v 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 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) ] icalendar-3.6.1/docs/credits.rst000066400000000000000000000025361226475721100165770ustar00rootroot00000000000000icalendar contributors ====================== - Johannes Raggam (Maintainer) - Max M (Original author) - Andreas Zeidler - Andrey Nikolaev - Barak Michener - Christophe de Vienne - Christian Geier - Dai MIKURUBE - Dan Stovall - Eric Hanchrow - Erik Simmler - George V. Reilly - Jannis Leidel - Jeroen van Meeuwen (Kolab Systems) - Lennart Regebro - Marc Egli - Martijn Faassen - Martin Melin - Michael Smith - Mikael Frykholm - Olivier Grisel - Pavel Repin - Pedro Ferreira - Rembane - Robert Niederreiter - Rok Garbas - Ronan Dunklau - Sidnei da Silva - Stanislav Ochotnicky - Stefan Schwarzer - Victor Varvaryuk - Wichert Akkerman - spanktar - tgecho icalendar-3.6.1/docs/index.rst000066400000000000000000000002621226475721100162430ustar00rootroot00000000000000 .. include:: ../README.rst Contents ======== .. toctree:: :maxdepth: 2 about install usage RFC 5545 changelog credits license icalendar-3.6.1/docs/install.rst000066400000000000000000000003371226475721100166050ustar00rootroot00000000000000Installing 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 icalendar-3.6.1/docs/license.rst000066400000000000000000000000341226475721100165530ustar00rootroot00000000000000.. include:: ../LICENSE.rst icalendar-3.6.1/docs/usage.rst000066400000000000000000000225171226475721100162470ustar00rootroot00000000000000iCalendar 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=)' 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. icalendar-3.6.1/setup.py000066400000000000000000000030171226475721100151650ustar00rootroot00000000000000import 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 } ) icalendar-3.6.1/src/000077500000000000000000000000001226475721100142415ustar00rootroot00000000000000icalendar-3.6.1/src/icalendar/000077500000000000000000000000001226475721100161635ustar00rootroot00000000000000icalendar-3.6.1/src/icalendar/__init__.py000066400000000000000000000021751226475721100203010ustar00rootroot00000000000000from 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, ] icalendar-3.6.1/src/icalendar/cal.py000066400000000000000000000430011226475721100172720ustar00rootroot00000000000000# -*- 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() icalendar-3.6.1/src/icalendar/caselessdict.py000066400000000000000000000063101226475721100212030ustar00rootroot00000000000000# -*- 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) icalendar-3.6.1/src/icalendar/compat.py000066400000000000000000000005161226475721100200220ustar00rootroot00000000000000import 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)) icalendar-3.6.1/src/icalendar/parser.py000066400000000000000000000301431226475721100200320ustar00rootroot00000000000000# -*- 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 icalendar-3.6.1/src/icalendar/parser_tools.py000066400000000000000000000021061226475721100212500ustar00rootroot00000000000000from 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 icalendar-3.6.1/src/icalendar/prop.py000066400000000000000000000735701226475721100175310ustar00rootroot00000000000000# -*- 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[+-]?)(?P[\d]?)' '(?P[\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 icalendar-3.6.1/src/icalendar/tests/000077500000000000000000000000001226475721100173255ustar00rootroot00000000000000icalendar-3.6.1/src/icalendar/tests/__init__.py000066400000000000000000000002271226475721100214370ustar00rootroot00000000000000# unittest/unittest2 importer import unittest if not hasattr(unittest.TestCase, 'assertIsNotNone'): import unittest2 as unittest unittest # pep 8 icalendar-3.6.1/src/icalendar/tests/encoding.ics000066400000000000000000000006641226475721100216210ustar00rootroot00000000000000BEGIN: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 icalendar-3.6.1/src/icalendar/tests/issue_112_missing_tzinfo_on_exdate.ics000066400000000000000000000023121226475721100267060ustar00rootroot00000000000000BEGIN: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 icalendar-3.6.1/src/icalendar/tests/issue_114_invalid_line.ics000066400000000000000000000040361226475721100242620ustar00rootroot00000000000000BEGIN: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:\n\n\n\n\n\n\n\n\n

Qu and : vendredi 27 septembre 2013 15:00-16:00 (UTC+01:00) Bruxelles\, Copenh ague\, Madrid\, Paris.

\n\n

Emplacement : Conf-Call - 01 xx xx xx xx

\n\n

Remarque : le décalage GMT ci-dessus ne tient pas compte des réglages de l'heure d'été.

\n\n

*~*~*~*~*~*~*~*~ *~*

\n\n

Co mme convenu à l’instant par e-mail

\n\n\n X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE X-MICROSOFT-CDO-IMPORTANCE:1 X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY X END:VEVENT icalendar-3.6.1/src/icalendar/tests/issue_53_parsing_failure.ics000066400000000000000000000045751226475721100247310ustar00rootroot00000000000000BEGIN: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 icalendar-3.6.1/src/icalendar/tests/multiple.ics000066400000000000000000000016641226475721100216670ustar00rootroot00000000000000BEGIN: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 icalendar-3.6.1/src/icalendar/tests/recurrence.ics000066400000000000000000000013421226475721100221620ustar00rootroot00000000000000BEGIN: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 icalendar-3.6.1/src/icalendar/tests/test_encoding.py000066400000000000000000000061711226475721100225310ustar00rootroot00000000000000# -*- 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' ) icalendar-3.6.1/src/icalendar/tests/test_fixed_issues.py000066400000000000000000000204371226475721100234360ustar00rootroot00000000000000# -*- 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() ) icalendar-3.6.1/src/icalendar/tests/test_icalendar.py000066400000000000000000000227061226475721100226670ustar00rootroot00000000000000# 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"') icalendar-3.6.1/src/icalendar/tests/test_multiple.py000066400000000000000000000015561226475721100226000ustar00rootroot00000000000000from 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') ) icalendar-3.6.1/src/icalendar/tests/test_property_params.py000066400000000000000000000174431226475721100241760ustar00rootroot00000000000000# 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') icalendar-3.6.1/src/icalendar/tests/test_recurrence.py000066400000000000000000000040101226475721100230660ustar00rootroot00000000000000# -*- 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! icalendar-3.6.1/src/icalendar/tests/test_time.py000066400000000000000000000016011226475721100216720ustar00rootroot00000000000000from 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()) icalendar-3.6.1/src/icalendar/tests/test_timezoned.py000066400000000000000000000125471226475721100227450ustar00rootroot00000000000000# -*- 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') icalendar-3.6.1/src/icalendar/tests/test_unit_cal.py000066400000000000000000000320571226475721100225430ustar00rootroot00000000000000from 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']) icalendar-3.6.1/src/icalendar/tests/test_unit_caselessdict.py000066400000000000000000000055151226475721100244510ustar00rootroot00000000000000from 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']) icalendar-3.6.1/src/icalendar/tests/test_unit_parser_tools.py000066400000000000000000000021651226475721100245150ustar00rootroot00000000000000# -*- 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) icalendar-3.6.1/src/icalendar/tests/test_unit_prop.py000066400000000000000000000445341226475721100227670ustar00rootroot00000000000000# -*- 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) icalendar-3.6.1/src/icalendar/tests/test_unit_tools.py000066400000000000000000000015521226475721100231400ustar00rootroot00000000000000from 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) icalendar-3.6.1/src/icalendar/tests/time.ics000066400000000000000000000000731226475721100207630ustar00rootroot00000000000000BEGIN:VCALENDAR X-SOMETIME;VALUE=TIME:172010 END:VCALENDAR icalendar-3.6.1/src/icalendar/tests/timezoned.ics000066400000000000000000000014541226475721100220270ustar00rootroot00000000000000BEGIN: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 icalendar-3.6.1/src/icalendar/tools.py000066400000000000000000000020321226475721100176720ustar00rootroot00000000000000from 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)) icalendar-3.6.1/tox.ini000066400000000000000000000007621226475721100147720ustar00rootroot00000000000000[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*