pax_global_header00006660000000000000000000000064150130277330014513gustar00rootroot0000000000000052 comment=29e13c4815107bb54fc3d01dda8fcbde12044f9f icalendar-6.3.1/000077500000000000000000000000001501302773300134445ustar00rootroot00000000000000icalendar-6.3.1/.github/000077500000000000000000000000001501302773300150045ustar00rootroot00000000000000icalendar-6.3.1/.github/FUNDING.yml000066400000000000000000000015201501302773300166170ustar00rootroot00000000000000# These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: python-icalendar ko_fi: # Replace with a single Ko-fi username tidelift: pypi/icalendar # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry polar: # Replace with a single Polar username buy_me_a_coffee: # Replace with a single Buy Me a Coffee username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] icalendar-6.3.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001501302773300171675ustar00rootroot00000000000000icalendar-6.3.1/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000023031501302773300216570ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: "[BUG] " labels: '' assignees: '' --- ## Describe the bug ## To Reproduce ``` import icalendar ... ``` Output: ``` ``` ## Expected behavior ## Environment - [ ] OS: - [ ] Python version: - [ ] `icalendar` version: ## Additional context - [ ] I tested it with the latest version `pip3 install https://github.com/collective/icalendar.git` - [ ] I attached the ICS source file or there is no ICS source file icalendar-6.3.1/.github/ISSUE_TEMPLATE/empty-issue.md000066400000000000000000000007521501302773300220010ustar00rootroot00000000000000--- name: empty issue about: This is an unstructured issue template to use with issues that are not described, yet. title: '' labels: '' assignees: '' --- icalendar-6.3.1/.github/dependabot.yml000066400000000000000000000010061501302773300176310ustar00rootroot00000000000000# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem version: 2 updates: - package-ecosystem: github-actions directory: / groups: github-actions: patterns: - "*" # Group all Actions updates into a single larger pull request schedule: interval: weekly icalendar-6.3.1/.github/workflows/000077500000000000000000000000001501302773300170415ustar00rootroot00000000000000icalendar-6.3.1/.github/workflows/cifuzz.yml000066400000000000000000000023561501302773300211040ustar00rootroot00000000000000name: CIFuzz on: [pull_request] permissions: {} jobs: Fuzzing: runs-on: ubuntu-latest permissions: security-events: write steps: - name: Build Fuzzers id: build uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master with: oss-fuzz-project-name: 'icalendar' language: python - name: Run Fuzzers uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master with: oss-fuzz-project-name: 'icalendar' language: python fuzz-seconds: 600 output-sarif: true - name: Upload Crash uses: actions/upload-artifact@v4 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts - name: Upload Sarif if: always() && steps.build.outcome == 'success' uses: github/codeql-action/upload-sarif@v3 with: # Path to SARIF file relative to the root of the repository sarif_file: cifuzz-sarif/results.sarif checkout_path: cifuzz-sarif check-distribution: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Do not include Apache files in the distribution. run: | src/icalendar/tests/test_create_release.sh icalendar-6.3.1/.github/workflows/rtd-pr-preview.yml000066400000000000000000000010551501302773300224540ustar00rootroot00000000000000# .github/workflows/rtd-pr-preview.yml name: readthedocs/actions on: pull_request_target: types: - opened # Execute this action only on PRs that touch # documentation files. paths: - "docs/**" - "*.rst" - "src/icalendar/*.py" - .readthedocs.yaml - requirements_docs.txt permissions: pull-requests: write jobs: documentation-links: runs-on: ubuntu-latest steps: - uses: readthedocs/actions/preview@v1 with: project-slug: "icalendar" single-version: "true" icalendar-6.3.1/.github/workflows/ruff.yml000066400000000000000000000003021501302773300205210ustar00rootroot00000000000000name: Ruff on: [push, pull_request] jobs: ruff: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: chartboost/ruff-action@v1 continue-on-error: true icalendar-6.3.1/.github/workflows/tests.yml000066400000000000000000000121071501302773300207270ustar00rootroot00000000000000name: tests on: push: branches: - main tags: - v* pull_request: schedule: - cron: '14 7 * * 0' # run once a week on Sunday workflow_dispatch: jobs: run-tests: strategy: matrix: config: # [Python version, tox env] - ["3.8", "py38"] - ["3.8", "nopytz"] - ["3.9", "py39"] - ["3.10", "py310"] - ["pypy-3.9", "pypy3"] # - ["3.10", "docs"] # disable as readthedocs builds it - ["3.11", "py311"] - ["3.12", "py312"] - ["3.13", "py313"] runs-on: ubuntu-latest name: ${{ matrix.config[1] }} steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.config[0] }} # for caching, see # https://github.com/actions/setup-python/blob/main/docs/advanced-usage.md#caching-packages cache: 'pip' cache-dependency-path: | setup.* tox.ini requirements*.txt - name: Install dependencies run: | python -m pip install --upgrade pip pip install tox coveralls coverage-python-version - name: Test run: tox -e ${{ matrix.config[1] }} - name: Coveralls Parallel uses: coverallsapp/github-action@v2 with: flag-name: run-${{ matrix.config[1] }} parallel: true file: coverage.xml allow-empty: true coverage: # parallel test coverage upload # see https://coveralls-python.readthedocs.io/en/latest/usage/configuration.html#github-actions-support name: Submit test coverage needs: run-tests # always finalize coverage aftest tests ran # see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-not-requiring-successful-dependent-jobs if: ${{ always() }} runs-on: ubuntu-latest steps: - name: Coveralls Finished uses: coverallsapp/github-action@v2 with: parallel-finished: true format: cobertura test-distribution: name: Check built package runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.13" - name: Install dependencies run: | python -m pip install --upgrade pip pip install build twine - name: Build distribution files run: python -m build # same as in deploy-tag-to-pypi - name: Check distribution files run: twine check dist/* deploy-tag-to-pypi: # only deploy on tags, see https://stackoverflow.com/a/58478262/1320237 if: startsWith(github.ref, 'refs/tags/v') needs: - run-tests - test-distribution runs-on: ubuntu-latest # This environment stores the TWINE_USERNAME and TWINE_PASSWORD # see https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment environment: name: PyPI url: https://pypi.org/project/icalendar/ # after using the environment, we need to make the secrets available # see https://docs.github.com/en/actions/security-guides/encrypted-secrets#example-using-bash env: TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.13" - name: Install dependencies run: | python -m pip install --upgrade pip pip install build twine - name: remove old files run: rm -rf dist/* - name: build distribution files run: python -m build # same as in test-distribution - name: deploy to pypi run: | # You will have to set the variables TWINE_USERNAME and TWINE_PASSWORD # You can use a token specific to your project by setting the user name to # __token__ and the password to the token given to you by the PyPI project. # sources: # - https://shambu2k.hashnode.dev/gitlab-to-pypi # - http://blog.octomy.org/2020/11/deploying-python-pacakges-to-pypi-using.html?m=1 if [ -z "$TWINE_USERNAME" ]; then echo "WARNING: TWINE_USERNAME not set!" fi if [ -z "$TWINE_PASSWORD" ]; then echo "WARNING: TWINE_PASSWORD not set!" fi twine check dist/* twine upload dist/* deploy-github-release: # only deploy on tags, see https://stackoverflow.com/a/58478262/1320237 if: startsWith(github.ref, 'refs/tags/v') needs: - run-tests - test-distribution runs-on: ubuntu-latest environment: name: github-release steps: - uses: actions/checkout@v4 - name: Create GitHub release from tag uses: ncipollo/release-action@v1 with: allowUpdates: true body: "To view the changes, please see the [Changelog](https://icalendar.readthedocs.io/en/latest/changelog.html). This release can be installed from [PyPI](https://pypi.org/project/icalendar/#history)." generateReleaseNotes: false icalendar-6.3.1/.gitignore000066400000000000000000000004151501302773300154340ustar00rootroot00000000000000*.mo *.py? *.sw? *~ .* bin/ build/ coverage-* develop-eggs/ dist/ docs/_build/ eggs/ htmlcov/ include/ lib/ lib64 parts/ pip-selfcheck.json src/icalendar.egg-info/ !.gitattributes !.github !.gitignore venv /ical_fuzzer.pkg.spec /src/icalendar/_version.py coverage.xml icalendar-6.3.1/.pre-commit-config.yaml000066400000000000000000000005301501302773300177230ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: debug-statements - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.11.9 hooks: - id: ruff-check args: [--config, "pyproject.toml", --fix] - id: ruff-format args: [--config, "pyproject.toml"] icalendar-6.3.1/.readthedocs.yml000066400000000000000000000024171501302773300165360ustar00rootroot00000000000000# .readthedocs.yml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py # Optionally build your docs in additional formats such as PDF and ePub formats: all build: os: ubuntu-22.04 tools: python: "3.11" commands: - git fetch --tags - pip install build - python -m build # Cancel building pull requests when there aren't changes in the docs directory or YAML file. # You can add any other files or directories that you'd like here as well, # like your docs requirements file, or other files that will change your docs build. # # If there are no changes (git diff exits with 0) we force the command to return with 183. # This is a special exit code on Read the Docs that will cancel the build immediately. - | if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && git diff --quiet origin/main -- . docs/ src/icalendar/*.py *.rst .readthedocs.yaml requirements_docs.txt ':!src/icalendar/fuzzing' ':!src/icalendar/tests' ':!src/icalendar/timezone'; then exit 183; fi - pip install -r requirements_docs.txt - cd docs && make rtd-pr-preview icalendar-6.3.1/CHANGES.rst000066400000000000000000001117241501302773300152540ustar00rootroot00000000000000Changelog ========= We use `Semantic Versioning `_. - Breaking changes increase the **major** version number. - New features increase the **minor** version number. - Minor changes and bug fixes increase the **patch** version number. 6.3.1 (2025-05-20) ------------------ Bug fixes: - Remove forced quoting from parameters with space and single quote. See `Issue 836 `_. 6.3.0 (2025-05-15) ------------------ Minor changes: - Deprecate ``icalendar.UIDGenerator``. See `Issue 816 `_. New features: - Add the ``uid`` property to ``Alarm``, ``Event``, ``Calendar``, ``Todo``, and ``Journal`` components. See `Issue 740 `_. Bug fixes: - Fix component equality where timezones differ for the datetimes but the times are actually equal. See `Issue 828 `_. - Test that we can add an RRULE as a string. See `Issue 301 `_. - Test that we support dateutil timezones as outlined in `Issue 336 `_. - Build documentation on Read the Docs with the version identifier. See `Issue 826 `_. 6.2.0 (2025-05-07) ------------------ Minor changes: - Use ``ruff`` to format the source code. - Update project metadata to use License-Expression. - Use ``tzp.localize(dt, None)`` to remove the timezone from a datetime. - Remove the HTML documentation when building with ``tox`` to force rebuild. - Switch to PyData Sphinx Theme for documentation. See `Issue 803 `_. New features: - Add getters ``rrules``, ``rdates``, and ``exdates`` for unified and simple access to these properties. See `Issue 662`_. - Add attributes to the calendar for properties ``NAME``, ``DESCRIPTION``, and ``COLOR``. See `Issue 655 `_. - Add a ``color`` attribute to ``Event``, ``Todo``, and ``Journal`` components. See `Issue 655`_. - Add ``sequence`` attribute to ``Event``, ``Todo``, and ``Journal`` components. See `Issue 802 `_. - Add ``categories`` attribute to ``Calendar``, ``Event``, ``Todo``, and ``Journal`` components. See `Issue 655 `_. - Add compatibility to :rfc:`6868`. See `Issue 652 `_. - Add ``freebusy`` property to the ``Calendar`` to get this type of subcomponents easier. - Add parameters from :rfc:`5545` to properties ``ALTREP``, ``CN``, ``CUTYPE``, ``DELEGATED_FROM``, ``DELEGATED_TO``, ``DIR``, ``FBTYPE``, ``LANGUAGE``, ``MEMBER``, ``PARTSTAT``, ``RANGE``, ``RELATED``, ``ROLE``, ``RSVP``, ``SENT_BY``, ``TZID``, and ``RELTYPE``. See `Issue 798 `_. - New properties from :rfc:`7986` can occur multiple times in ``VCALENDAR``. See `PR 808`_. Bug fixes: - Fix ``STANDARD`` and ``DAYLIGHT`` components that have a date as ``DTSTART``. See `Issue 218 `_ - Move import at the end of ``icalendar.parser`` into a function to mitigate import errors, see `Issue 781 `_. - ``ALTREP``, ``DELEGATED-FROM``, ``DELEGATED-TO``, ``DIR``, ``MEMBER``, and ``SENT-BY`` require double quotes. These are now always added. - Classify ``CATEGORIES`` as multiple in ``VEVENT``. See `PR 808 `_. 6.1.3 (2025-03-19) ------------------ Bug fixes: - Fix to permit TZID forward references to VTIMEZONEs - Stabelize timezone id lookup, see `Issue 780 `_. 6.1.2 (2025-03-19) ------------------ Minor changes: - Add funding link to Tidelift. - Link to related package. - Shorten first example in documentation. - Add ``name`` and ``email`` properties to ``vCalAddress``. - Add type hint for property ``params`` in ``icalendar.prop``. - Set default value for ``params`` as ``params={}`` in mulitple constructors in ``icalendar.prop`` to improve usability. - Improve object initialization performance in ``icalendar.prop``. - Add type hint for ``params`` in multiple constructors in ``icalendar.prop``. Bug fixes: - Restrict timezones tested, see `Issue 763 `_ 6.1.1 (2025-01-18) ------------------ Minor changes: - Add a ``weekday`` attribute to :class:`icalendar.prop.vWeekday` components. See `Issue 749 `_. - Document :class:`icalendar.prop.vRecur` property. See `Issue 758 `_. - Print failure of doctest to aid debugging. - Improve documentation of :class:`icalendar.prop.vGeo` - Fix tests, improve code readability, fix typing. See `Issue 766 `_ and `Issue 765 `_. Breaking changes: - The ``relative`` attribute of ``vWeekday`` components has the correct sign now. See `Issue 749 `_. New features: - Add :ref:`Security Policy` - Python types in documentation now link to their documentation pages using ``intersphinx``. 6.1.0 (2024-11-22) ------------------ Minor changes: - Add ``end``, ``start``, ``duration``, ``DTSTART``, ``DUE``, and ``DURATION`` attributes to ``Todo`` components. See `Issue 662`_. - Add ``DTSTART``, ``TZOFFSETTO`` and ``TZOFFSETFROM`` properties to ``TimezoneStandard`` and ``TimezoneDaylight``. See `Issue 662`_. - Format test code with Ruff. See `Issue 672 `_. - Document the Debian package. See `Issue 701 `_. - Document ``vDatetime.from_ical`` - Allow passing a ``datetime.date`` to ``TZP.localize_utc`` and ``TZP.localize`` methods. - Document component classes with description from :rfc:`5545`. - Merge "File Structure" and "Overview" sections in the docs. See `Issue 626 `_. - Update code blocks in ``usage.rst`` with the correct lexer. - Tidy up the docstring for ``icalendar.prop.vPeriod``. - Improve typing and fix typing issues New features: - Add ``VALARM`` properties for :rfc:`9074`. See `Issue 657 `_ - Test compatibility with Python 3.13 - Add ``Timezone.from_tzinfo()`` and ``Timezone.from_tzid()`` to create a ``Timezone`` component from a ``datetime.tzinfo`` timezone. See `Issue 722`_. - Add ``icalendar.prop.tzid_from_tzinfo``. - Add ``icalendar.alarms`` module to calculate alarm times. See `Issue 716 `_. - Add ``Event.alarms`` and ``Todo.alarms`` to access alarm calculation. - Add ``Component.DTSTAMP`` and ``Component.LAST_MODIFIED`` properties for datetime in UTC. - Add ``Component.is_thunderbird()`` to check if the component uses custom properties by Thunderbird. - Add ``X_MOZ_SNOOZE_TIME`` and ``X_MOZ_LASTACK`` properties to ``Event`` and ``Todo``. - Add ``Alarm.ACKNOWLEDGED``, ``Alarm.TRIGGER``, ``Alarm.REPEAT``, and ``Alarm.DURATION`` properties as well as ``Alarm.triggers`` to calculate alarm triggers. - Add ``__doc__`` string documentation for ``vDate``, ``vBoolean``, ``vCalAddress``, ``vDuration``, ``vFloat``, ``vGeo``, ``vInt``, ``vPeriod``, ``vTime``, ``vUTCOffset`` and ``vUri``. See `Issue 742 `_. - Add ``DTSTART``, ``TZOFFSETTO``, and ``TZOFFSETFROM`` to ``TimezoneStandard`` and ``TimezoneDaylight`` - Use ``example`` methods of components without arguments. - Add ``events``, ``timezones``, and ``todos`` property to ``Calendar`` for nicer access. - To calculate which timezones are in use and add them to the ``Calendar`` when needed these methods are added: ``get_used_tzids``, ``get_missing_tzids``, and ``add_missing_timezones``. - Identify the TZID of more timezones from dateutil. - Identify totally unknown timezones using a UTC offset lookup tree generated in ``icalendar.timezone.equivalent_timezone_ids`` and stored in ``icalendar.timezone.equivalent_timezone_ids``. - Add ``icalendar.timezone.tzid`` to identify a timezone's TZID. Bug fixes: - Add ``icalendar.timezone`` to the documentation. .. _`Issue 722`: https://github.com/collective/icalendar/issues/722 6.0.1 (2024-10-13) ------------------ New features: - Added ``end``, ``start``, ``duration``, ``DTSTART``, ``DUE``, and ``DURATION`` attributes to ``Event`` components. See `Issue 662`_. - Added ``end``, ``start``, ``duration``, and ``DTSTART`` attributes to ``Journal`` components. See `Issue 662`_. Bug fixes: - Fix a few ``__all__`` variables. - Added missing ``docs`` folder to distribution packages. See `Issue 712 `_. .. _`Issue 662`: https://github.com/collective/icalendar/issues/662 6.0.0 (2024-09-28) ------------------ Minor changes: - Add ``__all__`` variable to each modules in ``icalendar`` package - Improve test coverage. - Adapt ``test_with_doctest.py`` to correctly run on Windows. - Measure branch coverage when running tests. - Export ``Component`` base class for typing New features: - Use ``pyproject.toml`` file instead of ``setup.py`` Bug fixes: - Fix link to stable release of tox in documentation. - Fix a bad ``bytes`` replace in ``unescape_char``. - Handle ``ValueError`` in ``vBinary.from_ical``. - Ignore the BOM character in incorrectly encoded ics files. 6.0.0a0 (2024-07-03) -------------------- Minor changes: - Test that all code works with both ``pytz`` and ``zoneinfo``. - Add message to GitHub release, pointing to the changelog - Make coverage report submission optional for pull requests - Parallelize coverage - Rename ``master`` branch to ``main``, see `Issue `_ - Update ``docs/usage.rst`` to use zoneinfo instead of pytz. - Added missing public classes and functions to API documentation. - Improved namespace management in the ``icalendar`` directory. - Add Python version badge and badge for test coverage - Remove 4.x badge - Update list of ``tox`` environments - Use Coveralls' GitHub Action - Check distribution in CI Breaking changes: - Use ``zoneinfo`` for ``icalendar`` objects created from strings, see `Issue #609 `_. This is an tested extension of the functionality, not a restriction: If you create ``icalendar`` objects with ``pytz`` timezones in your code, ``icalendar`` will continue to work in the same way. Your code is not affected. ``zoneinfo`` will be used for those **objects that** ``icalendar`` **creates itself**. This happens for example when parsing an ``.ics`` file, strings or bytes with ``from_ical()``. If you rely on ``icalendar`` providing timezones from ``pytz``, you can add one line to your code to get the behavior of versions below 6: .. code:: Python import icalendar icalendar.use_pytz() - Replaced ``pkg_resources.get_distribution`` with ``importlib.metadata`` in ``docs/conf.py`` to allow building docs on Python 3.12. - Remove ``is_broken`` property. Use ``errors`` instead to check if a component had suppressed parsing errors. See `Issue 424 `_. - Remove untested and broken ``LocalTimezone`` and ``FixedOffset`` tzinfo sub-classes, see `Issue 67 `_ - Remove Python 3.7 as compatible. icalendar is compatible with Python versions 3.8 - 3.12, and PyPy3. - Remove ``pytz`` as a dependency of ``icalendar``. If you require ``pytz``, add it to your dependency list or install it additionally with:: pip install icalendar==6.* pytz New features: - Check code quality with `Ruff `_, optional report - Test compatibility with Python 3.12 - Add function ``icalendar.use_pytz()``. - Allows selecting components with ``walk(select=func)`` where ``func`` takes a component and returns ``True`` or ``False``. - Add compatibility to :rfc:`7529`, adding ``vMonth`` and ``vSkip`` - Add ``sphinx-autobuild`` for ``livehtml`` Makefile target. - Add pull request preview on Read the Docs, building only on changes to documentation-related files. - Add link to pull request preview builds in the pull request description only when there are changes to documentation-related files. - Add documentation of live HTML preview of documentation and clean up of ``install.rst``. - Add ``sphinx-copybutton`` to allow copying code blocks with a single click of a button. Bug fixes: - Change documentation to represent compatibility with Python 3.8 - 3.12, and PyPy3. - Rename RFC 2445 to RFC 5545, see `Issue 278 `_ 5.0.13 (2024-06-20) ------------------- Minor changes: - Guide to delete the build folder before running tests - Add funding information - Make documentation build with Python 3.12 - Update windows to olson conversion for Greenland Standard Time - Extend examples in Usage with alarm and recurrence - Document how to serve the built documentation to view with the browser - Improve test coverage New features: - Create GitHub releases for each tag. Bug fixes: - Parse calendars with X-COMMENT properties at the end the file by ignoring these properites 5.0.12 (2024-03-19) ------------------- Minor changes: - Analyse code coverage of test files - Added corpus to fuzzing directory - Added exclusion of fuzzing corpus in MANIFEST.in - Augmented fuzzer to optionally convert multiple calendars from a source string - Add script to convert OSS FUZZ test cases to Python/pytest test cases - Added additional exception handling of defined errors to fuzzer, to allow fuzzer to explore deeper - Added more instrumentation to fuzz-harness - Rename "contributor" to "collaborator" in documentation - Correct the outdated "icalendar view myfile.ics" command in documentation. #588 - Update GitHub Actions steps versions - Keep GitHub Actions up to date with GitHub's Dependabot Bug fixes: - Fixed index error in cal.py when attempting to pop from an empty stack - Fixed type error in prop.py when attempting to join strings into a byte-string - Caught Wrong Date Format in ical_fuzzer to resolve fuzzing coverage blocker 5.0.11 (2023-11-03) ------------------- Minor changes: - The cli utility now displays start and end datetimes in the user's local timezone. Ref: #561 [vimpostor] New features: - Added fuzzing harnesses, for integration to OSSFuzz. - icalendar releases are deployed to Github releases Fixes: #563 [jacadzaca] Bug fixes: - CATEGORIES field now accepts a string as argument Ref: #322 [jacadzaca] - Multivalue FREEBUSY property is now parsed properly Ref: #27 [jacadzaca] - Compare equality and inequality of calendars more completely Ref: #570 - Use non legacy timezone name. Ref: #567 - Add some compare functions. Ref: #568 - Change OSS Fuzz build script to point to harnesses in fuzzing directory Ref: #574 5.0.10 (2023-09-26) ------------------- Bug fixes: - Component._encode stops ignoring parameters argument on native values, now merges them Fixes: #557 [zocker1999net] 5.0.9 (2023-09-24) ------------------ Bug fixes: - PERIOD values now set the timezone of their start and end. #556 5.0.8 (2023-09-18) ------------------ Minor changes: - Update build configuration to build readthedocs. #538 - No longer run the ``plone.app.event`` tests. - Add documentation on how to parse ``.ics`` files. #152 - Move pip caching into Python setup action. - Check that issue #165 can be closed. - Updated about.rst for issue #527 - Avoid ``vText.__repr__`` BytesWarning. Bug fixes: - Calendar components are now properly compared Ref: #550 Fixes: #526 [jacadzaca] 5.0.7 (2023-05-29) ------------------ Bug fixes: - to_ical() now accepts RRULE BYDAY values>=10 #518 5.0.6 (2023-05-26) ------------------ Minor changes: - Adjusted duration regex 5.0.5 (2023-04-13) ------------------ Minor changes: - Added support for BYWEEKDAY in vRecur ref: #268 Bug fixes: - Fix problem with ORGANIZER in FREE/BUSY #348 5.0.4 (2022-12-29) ------------------ Minor changes: - Improved documentation Ref: #503, #504 Bug fixes: - vBoolean can now be used as an parameter Ref: #501 Fixes: #500 [jacadzaca] 5.0.3 (2022-11-23) ------------------ New features: - vDDDTypes is hashable #487 #492 [niccokunzmann] Bug fixes: - vDDDTypes' equality also checks the dt attribute #497 #492 [niccokunzmann] 5.0.2 (2022-11-03) ------------------ Minor changes: - Refactored cal.py, tools.py and completed remaining minimal refactoring in parser.py. Ref: #481 [pronoym99] - Calendar.from_ical no longer throws long errors Ref: #473 Fixes: #472 [jacadzaca] - Make datetime value shorter by removing the value parameter where possible. Fixes: #318 [jacadzaca], [niccokunzmann] New features: - source code in documentation is tested using doctest #445 [niccokunzmann] Bug fixes: - broken properties are not added to the parent component Ref: #471 Fixes: #464 [jacadzaca] 5.0.1 (2022-10-22) ------------------ Minor changes: - fixed setuptools deprecation warnings [mgorny] Bug fixes: - a well-known timezone timezone prefixed with a `/` is treated as if the slash wasn't present Ref: #467 Fixes: #466 [jacadzaca] 5.0.0 (2022-10-17) ------------------ Minor changes: - removed deprecated test checks [tuergeist] - Fix: cli does not support DURATION #354 [mamico] - Add changelog and contributing to readthedocs documentation #428 [peleccom] - fixed small typos #323 [rohnsha0] - unittest to parametrized pytest refactoring [jacadzaca] Breaking changes: - Require Python 3.7 as minimum Python version. [maurits] [niccokunzmann] - icalendar now takes a ics file directly as an input - icalendar's CLI utility program's output is different - Drop Support for Python 3.6. Versions 3.7 - 3.11 are supported and tested. New features: - icalendar utility outputs a 'Duration' row - icalendar can take multiple ics files as an input Bug fixes: - Changed tools.UIDGenerator instance methods to static methods Ref: #345 [spralja] - proper handling of datetime objects with `tzinfo` generated through zoneinfo.ZoneInfo. Ref: #334 Fixes: #333 [tobixen] - Timestamps in UTC does not need tzid Ref: #338 Fixes: #335 [tobixen] - add ``__eq__`` to ``icalendar.prop.vDDDTypes`` #391 [jacadzaca] - Refactor deprecated unittest aliases for Python 3.11 compatibility #330 [tirkarthi] 5.0.0a1 (2022-07-11) -------------------- Breaking changes: - Drop support for Python 3.4, 3.5 and PyPy2. [maurits] New features: - Document development setup Ref: #358 [niccokunzmann] Bug fixes: - Test with GitHub Actions. [maurits] 4.1.0 (2022-07-11) ------------------ New features: - No longer test on Python 3.4, 3.5 and PyPy2, because we cannot get it to work. Technically it should still work, it is just no longer tested. Do not expect much development on branch 4.x anymore. The main branch will be for the remaining Python versions that we support. [maurits] Bug fixes: - Test with GitHub Actions. [maurits] 4.0.9 (2021-10-16) ------------------ Bug fixes: - Fix vCategories for correct en/de coding. [thet] - vDuration property value: Fix changing duration sign after multiple ``to_ical`` calls. Ref: #320 Fixes: #319 [barlik] 4.0.8 (2021-10-07) ------------------ Bug fixes: - Support added for Python 3.9 and 3.10 (no code changes needed). - Replace bare 'except:' with 'except Exception:' (#281) 4.0.7 (2020-09-07) ------------------ Bug fixes: - fixed rrule handling, re-enabled test_create_america_new_york() 4.0.6 (2020-05-06) ------------------ Bug fixes: - Use ``vText`` as default type, when convert recurrence definition to ical string. [kam193] 4.0.5 (2020-03-21) ------------------ Bug fixes: - Fixed a docs issue related to building on Read the Docs [davidfischer] 4.0.4 (2019-11-25) ------------------ Bug fixes: - Reduce Hypothesis iterations to speed up testing, allowing PRs to pass [UniversalSuperBox] 4.0.3 (2018-10-10) ------------------ Bug fixes: - Categories are comma separated not 1 per line #265. [cleder] - mark test with mixed timezoneaware and naive datetimes as an expected failure. [cleder] 4.0.2 (2018-06-20) ------------------ Bug fixes: - Update all pypi.python.org URLs to pypi.org [jon.dufresne] 4.0.1 (2018-02-11) ------------------ - Added rudimentary command line interface. [jfjlaros] - Readme, setup and travis updates. [jdufresne, PabloCastellano] 4.0.0 (2017-11-08) ------------------ Breaking changes: - Drop support for Python 2.6 and 3.3. 3.12 (2017-11-07) ----------------- New features: - Accept Windows timezone identifiers as valid. #242 [geier] Bug fixes: - Fix ResourceWarnings in setup.py when Python warnings are enabled. #244 [jdufresne] - Fix invalid escape sequences in string and bytes literals. #245 [jdufresne] - Include license file in the generated wheel package. #243 [jdufresne] - Fix non-ASCII TZID and TZNAME parameter handling. #238 [clivest] - Docs: update install instructions. #240 [Ekran] 3.11.7 (2017-08-27) ------------------- New features: - added vUTCOffset.ignore_exceptions to allow surpressing of failed TZOFFSET parsing (for now this ignores the check for offsets > 24h) [geier] 3.11.6 (2017-08-04) ------------------- Bug fixes: - Fix VTIMEZONEs including RDATEs #234. [geier] 3.11.5 (2017-07-03) ------------------- Bug fixes: - added an assertion that VTIMEZONE sub-components' DTSTART must be of type DATETIME [geier] - Fix handling of VTIMEZONEs with subcomponents with the same DTSTARTs and OFFSETs but which are of different types [geier] 3.11.4 (2017-05-10) ------------------- Bug fixes: - Don't break on parameter values which contain equal signs, e.g. base64 encoded binary data [geier] - Fix handling of VTIMEZONEs with subcomponents with the same DTSTARTs. [geier] 3.11.3 (2017-02-15) ------------------- Bug fixes: - Removed ``setuptools`` as a dependency as it was only required by setup.py and not by the package. - Don't split content lines on the unicode ``LINE SEPARATOR`` character ``\u2028`` but only on ``CRLF`` or ``LF``. 3.11.2 (2017-01-12) ------------------- Bug fixes: - Run tests with python 3.5 and 3.6. [geier] - Allow tests failing with pypy3 on travis.ci. [geier] 3.11.1 (2016-12-19) ------------------- Bug fixes: - Encode error message before adding it to the stack of collected error messages. 3.11 (2016-11-18) ----------------- Fixes: - Successfully test with pypy and pypy3. [gforcada] - Minor documentation update. [tpltnt] 3.10 (2016-05-26) ----------------- New: - Updated components description to better comply with RFC 5545. Refs #183. [stlaz] - Added PERIOD value type to date types. Also fixes incompatibilities described in #184. Refs #189. [stlaz] Fixes: - Fix testsuite for use with ``dateutil>=2.5``. Refs #195. [untitaker] - Reintroduce cal.Component.is_broken that was removed with 3.9.2. Refs #185. [geier] 3.9.2 (2016-02-05) ------------------ New: - Defined ``test_suite`` in setup.py. Now tests can be run via ``python setup.py test``. [geier] Fixes: - Fixed cal.Component.from_ical() representing an unknown component as one of the known. [stlaz] - Fixed possible IndexError exception during parsing of an ical string. [stlaz] - When doing a boolean test on ``icalendar.cal.Component``, always return ``True``. Before it was returning ``False`` due to CaselessDict, if it didn't contain any items. [stlaz] - Fixed date-time being recognized as date or time during parsing. Added better error handling to parsing from ical strings. [stlaz] - Added __version__ attribute to init.py. [TomTry] - Documentation fixes. [TomTry] - Pep 8, UTF 8 headers, dict/list calls to literals. [thet] 3.9.1 (2015-09-08) ------------------ - Fix ``vPeriod.__repr__``. [spacekpe] - Improve foldline() performance. This improves the foldline performance, especially for large strings like base64-encoded inline attachements. In some cases (1MB string) from 7 Minutes to less than 20ms for ASCII data and 500ms for non-ASCII data. Ref: #163. [emfree] 3.9.0 (2015-03-24) ------------------ - Creating timezone objects from VTIMEZONE components. [geier] - Make ``python-dateutil`` a dependency. [geier] - Made RRULE tolerant of trailing semicolons. [sleeper] - Documentation fixes. [t-8ch, thet] 3.8.4 (2014-11-01) ------------------ - Add missing BYWEEKNO to recurrence rules. [russkel] 3.8.3 (2014-08-26) ------------------ - PERCENT property in VTODO renamed to PERCENT-COMPLETE, according to RFC5545. [thomascube] 3.8.2 (2014-07-22) ------------------ - Exclude editor backup files from egg distributions. Fixes #144. [thet] 3.8.1 (2014-07-17) ------------------ - The representation of CaselessDicts in 3.8 changed the name attribute of Components and therefore broke the external API. This has been fixed. [untitaker] 3.8 (2014-07-17) ---------------- - Allow dots in property names (Needed for vCard compatibility). Refs #143. [untitaker] - Change class representation for CaselessDict objects to always include the class name or the class' name attribute, if available. Also show subcomponents for Component objects. [thet] - Don't use data_encode for CaselessDict class representation but use dict's __repr__ method. [t-8ch] - Handle parameters with multiple values, which is needed for vCard 3.0. Refs #142. [t-8ch] 3.7 (2014-06-02) ---------------- - For components with ``ignore_exceptions`` set to ``True``, mark unparseable lines as broken instead rising a ``ValueError``. ``VEVENT`` components have ``ignore_exceptions`` set to ``True`` by default. Ref #131. Fixes #104. [jkiang13] - Make ``python-dateutil`` a soft-dependency. [boltnev] - Add optional ``sorted`` parameter to ``Component.to_ical``. Setting it to false allows the user to preserve the original property and parameter order. Ref #136. Fixes #133. [untitaker] - Fix tests for latest ``pytz``. Don't set ``tzinfo`` directly on datetime objects, but use pytz's ``localize`` function. Ref #138. [untitaker, thet] - Remove incorrect use of __all__. We don't encourage using ``from package import *`` imports. Fixes #129. [eric-wieser] 3.6.2 (2014-04-05) ------------------ - Pep8 and cleanup. [lasudry] 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 https://readthedocs.org service so sphinx documentation is generated on each commit (for main). Documentation can be visible on: https://icalendar.readthedocs.io/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-6.3.1/CONTRIBUTING.rst000066400000000000000000000014211501302773300161030ustar00rootroot00000000000000You 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 (https://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`` Setup for Development --------------------- If you would like to setup icalendar to contribute changes, the `Installation Section `_ should help you further. icalendar-6.3.1/LICENSE.rst000066400000000000000000000024561501302773300152670ustar00rootroot00000000000000License ======= 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-6.3.1/MANIFEST.in000066400000000000000000000002441501302773300152020ustar00rootroot00000000000000include *.rst tox.ini graft docs recursive-include src/icalendar * recursive-exclude src/icalendar *.pyc *~ recursive-exclude src/icalendar/fuzzing *.py *.sh *.ics icalendar-6.3.1/README.rst000066400000000000000000000157201501302773300151400ustar00rootroot00000000000000========================================================== Internet Calendaring and Scheduling (iCalendar) for Python ========================================================== The `icalendar`_ package is a :rfc:`5545` compatible parser/generator for iCalendar files. ---- :Homepage: https://icalendar.readthedocs.io :Community Discussions: https://github.com/collective/icalendar/discussions :Issue Tracker: https://github.com/collective/icalendar/issues :Code: https://github.com/collective/icalendar :Dependencies: `python-dateutil`_ and `tzdata`_. :License: `BSD`_ ---- .. image:: https://badge.fury.io/py/icalendar.svg :target: https://pypi.org/project/icalendar/ :alt: Python Package Version on PyPI .. image:: https://img.shields.io/pypi/pyversions/icalendar :target: https://pypi.org/project/icalendar/ :alt: PyPI - Python Version .. image:: https://img.shields.io/pypi/dm/icalendar.svg :target: https://pypi.org/project/icalendar/#files :alt: Downloads from PyPI .. image:: https://img.shields.io/github/actions/workflow/status/collective/icalendar/tests.yml?branch=main&label=main&logo=github :target: https://github.com/collective/icalendar/actions/workflows/tests.yml?query=branch%3Amain :alt: GitHub Actions build status for main .. image:: https://readthedocs.org/projects/icalendar/badge/?version=latest :target: https://icalendar.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://coveralls.io/repos/github/collective/icalendar/badge.svg :target: https://coveralls.io/github/collective/icalendar :alt: Test Coverage .. _`icalendar`: https://pypi.org/project/icalendar/ .. _`python-dateutil`: https://github.com/dateutil/dateutil/ .. _`tzdata`: https://pypi.org/project/tzdata/ .. _`BSD`: https://github.com/collective/icalendar/issues/2 Quick start guide ================= ``icalendar`` enables you to **create**, **inspect** and **modify** calendaring information with Python. To **install** the package, run:: pip install icalendar Inspect Files ------------- You can open an ``.ics`` file and see all the events: .. code:: python >>> import icalendar >>> from pathlib import Path >>> ics_path = Path("src/icalendar/tests/calendars/example.ics") >>> calendar = icalendar.Calendar.from_ical(ics_path.read_bytes()) >>> for event in calendar.events: ... print(event.get("SUMMARY")) New Year's Day Orthodox Christmas International Women's Day Modify Content -------------- Such a calendar can then be edited and saved again. .. code:: python >>> calendar.calendar_name = "My Modified Calendar" # modify >>> print(calendar.to_ical()[:121]) # save modification BEGIN:VCALENDAR VERSION:2.0 PRODID:collective/icalendar CALSCALE:GREGORIAN METHOD:PUBLISH NAME:My Modified Calendar Create Events, TODOs, Journals, Alarms, ... ------------------------------------------- ``icalendar`` supports the creation and parsing of all kinds of objects in the iCalendar (:rfc:`5545`) standard. .. code:: python >>> icalendar.Event() # events VEVENT({}) >>> icalendar.FreeBusy() # free/busy times VFREEBUSY({}) >>> icalendar.Todo() # Todo list entries VTODO({}) >>> icalendar.Alarm() # Alarms e.g. for events VALARM({}) >>> icalendar.Journal() # Journal entries VJOURNAL({}) Have a look at `more examples `_. Use timezones of your choice ---------------------------- With ``icalendar``, you can localize your events to take place in different timezones. ``zoneinfo``, ``dateutil.tz`` and ``pytz`` are compatible with ``icalendar``. This example creates an event that uses all of the timezone implementations with the same result: .. code:: python >>> import pytz, zoneinfo, dateutil.tz # timezone libraries >>> import datetime, icalendar >>> e = icalendar.Event() >>> tz = dateutil.tz.tzstr("Europe/London") >>> e["X-DT-DATEUTIL"] = icalendar.vDatetime(datetime.datetime(2024, 6, 19, 10, 1, tzinfo=tz)) >>> tz = pytz.timezone("Europe/London") >>> e["X-DT-USE-PYTZ"] = icalendar.vDatetime(datetime.datetime(2024, 6, 19, 10, 1, tzinfo=tz)) >>> tz = zoneinfo.ZoneInfo("Europe/London") >>> e["X-DT-ZONEINFO"] = icalendar.vDatetime(datetime.datetime(2024, 6, 19, 10, 1, tzinfo=tz)) >>> print(e.to_ical()) # the libraries yield the same result BEGIN:VEVENT X-DT-DATEUTIL;TZID=Europe/London:20240619T100100 X-DT-USE-PYTZ;TZID=Europe/London:20240619T100100 X-DT-ZONEINFO;TZID=Europe/London:20240619T100100 END:VEVENT Version 6 with zoneinfo ----------------------- Version 6 of ``icalendar`` switches the timezone implementation to ``zoneinfo``. This only affects you if you parse ``icalendar`` objects with ``from_ical()``. The functionality is extended and is tested since 6.0.0 with both timezone implementations ``pytz`` and ``zoneinfo``. By default and since 6.0.0, ``zoneinfo`` timezones are created. .. code:: python >>> dt = icalendar.Calendar.example("timezoned").events[0].start >>> dt.tzinfo ZoneInfo(key='Europe/Vienna') If you would like to continue to receive ``pytz`` timezones in parse results, you can receive all the latest updates, and switch back to earlier behavior: .. code:: python >>> icalendar.use_pytz() >>> dt = icalendar.Calendar.example("timezoned").events[0].start >>> dt.tzinfo Version 6 is on `branch main `_. It is compatible with Python versions 3.8 - 3.13, and PyPy3. We expect the ``main`` branch with versions ``6+`` to receive the latest updates and features. Related projects ================ * `icalevents `_. It is built on top of icalendar and allows you to query iCal files and get the events happening on specific dates. It manages recurrent events as well. * `recurring-ical-events `_. Library to query an ``icalendar.Calendar`` object for events and other components happening at a certain date or within a certain time. * `x-wr-timezone `_. Library and command line tool to make ``icalendar.Calendar`` objects and files from Google Calendar (using the non-standard ``X-WR-TIMEZONE`` property) compliant with the standard (:rfc:`5545`). * `ics-query `_. Command line tool to query iCalendar files for occurrences of events and other components. * `icalendar-compatibility `_ - access to event data compatible with RFC5545 and different implementations Further Reading =============== You can find out more about this project: * `Contributing`_ * `Changelog`_ * `License`_ .. _`Contributing`: https://icalendar.readthedocs.io/en/latest/contributing.html .. _`Changelog`: https://icalendar.readthedocs.io/en/latest/changelog.html .. _`License`: https://icalendar.readthedocs.io/en/latest/license.html icalendar-6.3.1/SECURITY.md000066400000000000000000000002721501302773300152360ustar00rootroot00000000000000# Security Policy Please find our [security policy in the documentation](https://icalendar.readthedocs.io/en/latest/security.html). See also: - [docs/security.rst](docs/security.rst) icalendar-6.3.1/bootstrap.py000066400000000000000000000136121501302773300160360ustar00rootroot00000000000000############################################################################## # # Copyright (c) 2006 Zope Foundation 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 import shutil import sys import tempfile from optparse import OptionParser tmpeggs = tempfile.mkdtemp() usage = """\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --find-links to point to local resources, you can keep this script from going over the network. """ parser = OptionParser(usage=usage) parser.add_option("-v", "--version", help="use a specific zc.buildout version") parser.add_option( "-t", "--accept-buildout-test-releases", dest="accept_buildout_test_releases", action="store_true", default=False, help=( "Normally, if you do not specify a --version, the " "bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " "even if they are alphas or betas." ), ) parser.add_option( "-c", "--config-file", help=("Specify the path to the buildout configuration " "file to be used."), ) parser.add_option( "-f", "--find-links", help=("Specify a URL to search for buildout releases") ) parser.add_option( "--allow-site-packages", action="store_true", default=False, help=("Let bootstrap.py use existing site packages"), ) options, args = parser.parse_args() ###################################################################### # load/install setuptools try: if options.allow_site_packages: import setuptools import pkg_resources from urllib.request import urlopen except ImportError: from urllib2 import urlopen ez = {} exec(urlopen("https://bootstrap.pypa.io/ez_setup.py").read(), ez) if not options.allow_site_packages: # ez_setup imports site, which adds site packages # this will remove them from the path to ensure that incompatible versions # of setuptools are not in the path import site # inside a virtualenv, there is no 'getsitepackages'. # We can't remove these reliably if hasattr(site, "getsitepackages"): for sitepackage_path in site.getsitepackages(): sys.path[:] = [x for x in sys.path if sitepackage_path not in x] setup_args = {"to_dir": tmpeggs, "download_delay": 0} ez["use_setuptools"](**setup_args) import setuptools import pkg_resources # This does not (always?) update the default working set. We will # do it. for path in sys.path: if path not in pkg_resources.working_set.entries: pkg_resources.working_set.add_entry(path) ###################################################################### # Install buildout ws = pkg_resources.working_set cmd = [ sys.executable, "-c", "from setuptools.command.easy_install import main; main()", "-mZqNxd", tmpeggs, ] find_links = os.environ.get( "bootstrap-testing-find-links", options.find_links or ( "https://downloads.buildout.org/" if options.accept_buildout_test_releases else None ), ) if find_links: cmd.extend(["-f", find_links]) setuptools_path = ws.find(pkg_resources.Requirement.parse("setuptools")).location requirement = "zc.buildout" version = options.version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = "*final-", "*final" def _final_version(parsed_version): for part in parsed_version: if (part.startswith("*")) and (part not in _final_parts): return False return True index = setuptools.package_index.PackageIndex(search_path=[setuptools_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) if index.obtain(req) is not None: best = [] bestv = None for dist in index[req.project_name]: distv = dist.parsed_version if _final_version(distv): if bestv is None or distv > bestv: best = [dist] bestv = distv elif distv == bestv: best.append(dist) if best: best.sort() version = best[-1].version if version: requirement = "==".join((requirement, version)) cmd.append(requirement) import subprocess if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0: raise Exception("Failed to execute command:\n%s" % repr(cmd)[1:-1]) ###################################################################### # Import and run buildout ws.add_entry(tmpeggs) ws.require(requirement) import zc.buildout.buildout if not [a for a in args if "=" not in a]: args.append("bootstrap") # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args[0:0] = ["-c", options.config_file] zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) icalendar-6.3.1/buildout.cfg000066400000000000000000000006641501302773300157620ustar00rootroot00000000000000[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} [coverage] recipe = collective.recipe.template input = inline: #!/bin/sh ./bin/test --coverage ../../coverage -v --auto-progress "$@" output = ${buildout:directory}/bin/coverage mode = 755 icalendar-6.3.1/docs/000077500000000000000000000000001501302773300143745ustar00rootroot00000000000000icalendar-6.3.1/docs/Makefile000066400000000000000000000140101501302773300160300ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXAUTOBUILD = sphinx-autobuild PAPER = BUILDDIR = _build LOCALESDIR = _locales # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext livehtml rtd-pr-preview help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " livehtml to view a live preview of 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 " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @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 docs/$(BUILDDIR)/html." rm -rf build manual: *.rst $(SPHINXBUILD) -b html -t manual . manual presentation: *.rst $(SPHINXBUILD) -b html -t presentation . presentation 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." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(LOCALESDIR) @echo @echo "Build finished. The message catalogs are in $(LOCALESDIR)." 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." livehtml: $(SPHINXAUTOBUILD) \ --watch ../src \ --ignore "*.swp" \ --port 8050 \ -b html . "$(BUILDDIR)/html" $(SPHINXOPTS) $(O) rtd-pr-preview: ## Build pull request preview on Read the Docs $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) ${READTHEDOCS_OUTPUT}/html/ icalendar-6.3.1/docs/about.rst000066400000000000000000000007051501302773300162420ustar00rootroot00000000000000About ===== `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. The ``icalendar`` package is an :rfc:`5545` compatible parser/generator for iCalendar files. .. _`Max M`: http://www.mxm.dk icalendar-6.3.1/docs/api.rst000066400000000000000000000024061501302773300157010ustar00rootroot00000000000000API Reference ------------- icalendar.attr ++++++++++++++ .. automodule:: icalendar.attr :members: icalendar.alarms ++++++++++++++++ .. automodule:: icalendar.alarms :members: icalendar.cal +++++++++++++ .. automodule:: icalendar.cal :members: icalendar.caselessdict ++++++++++++++++++++++ .. automodule:: icalendar.caselessdict :members: icalendar.cli +++++++++++++ .. automodule:: icalendar.cli :members: icalendar.enums +++++++++++++++ .. automodule:: icalendar.enums :members: icalendar.error +++++++++++++++ .. automodule:: icalendar.error :members: icalendar.param +++++++++++++++ .. automodule:: icalendar.param :members: icalendar.parser ++++++++++++++++ .. automodule:: icalendar.parser :members: icalendar.parser_tools ++++++++++++++++++++++ .. automodule:: icalendar.parser_tools :members: icalendar.prop ++++++++++++++ .. automodule:: icalendar.prop :members: icalendar.tools +++++++++++++++ .. automodule:: icalendar.tools :members: icalendar.timezone ++++++++++++++++++ .. automodule:: icalendar.timezone :members: .. automodule:: icalendar.timezone.tzid :members: .. automodule:: icalendar.timezone.tzp :members: icalendar.version +++++++++++++++++ .. automodule:: icalendar.version :members: icalendar-6.3.1/docs/changelog.rst000066400000000000000000000000341501302773300170520ustar00rootroot00000000000000.. include:: ../CHANGES.rst icalendar-6.3.1/docs/cli.rst000066400000000000000000000015551501302773300157030ustar00rootroot00000000000000iCalendar utility ================= To get more information about the command line interface, use the ``-h`` option:: $ icalendar -h view ---- To output a human readable summary of an event:: $ icalendar myfile.ics The output will look something like this:: Organiser: Secretary Attendees: John Doe Randy Summary: Yearly evaluation. When: Tue 14 Mar 2017 11:00-12:00 Location: Randy's office Comment: Reminder. Description: Your yearly evaluation is scheduled for next Tuesday. Please be on time. To use this in terminal based e-mail clients like mutt, add a new mime type (as root):: # cat << EOF > /usr/lib/mime/packages/icalendar text/calendar; icalendar '%s'; copiousoutput; description=iCalendar text; priority=2 EOF # update-mime icalendar-6.3.1/docs/conf.py000066400000000000000000000052651501302773300157030ustar00rootroot00000000000000# icalendar documentation build configuration file import importlib.metadata import datetime import os extensions = [ "sphinx.ext.autodoc", "sphinx.ext.coverage", "sphinx.ext.viewcode", "sphinx_copybutton", "sphinx.ext.intersphinx", "sphinx.ext.autosectionlabel", "sphinx.ext.napoleon", ] source_suffix = ".rst" master_doc = "index" project = "icalendar" this_year = datetime.date.today().year copyright = f"{this_year}, Plone Foundation" release = version = importlib.metadata.version("icalendar") # -- 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 = "pydata_sphinx_theme" html_theme_options = { "icon_links": [ { "name": "GitHub", "url": "https://github.com/collective/icalendar", "icon": "fa-brands fa-square-github", "type": "fontawesome", "attributes": { "target": "_blank", "rel": "noopener me", "class": "nav-link custom-fancy-css" } }, { "name": "PyPI", "url": "https://pypi.org/project/icalendar", "icon": "fa-custom fa-pypi", "type": "fontawesome", "attributes": { "target": "_blank", "rel": "noopener me", "class": "nav-link custom-fancy-css" } }, ], "navigation_with_keys": True, "search_bar_text": "Search", "show_nav_level": 2, "show_toc_level": 2, "use_edit_page_button": True, } pygments_style = "sphinx" html_context = { # "github_url": "https://github.com", # or your GitHub Enterprise site "github_user": "collective", "github_repo": "icalendar", "github_version": "main", "doc_path": "docs", } htmlhelp_basename = "icalendardoc" # -- Intersphinx configuration ---------------------------------- # This extension can generate automatic links to the documentation of objects # in other projects. Usage is simple: whenever Sphinx encounters a # cross-reference that has no matching target in the current documentation set, # it looks for targets in the documentation sets configured in # intersphinx_mapping. A reference like :py:class:`zipfile.ZipFile` can then # linkto the Python documentation for the ZipFile class, without you having to # specify where it is located exactly. # # https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html intersphinx_mapping = { "python": ("https://docs.python.org/3", None), } man_pages = [("index", "icalendar", "icalendar Documentation", ["Plone Foundation"], 1)] icalendar-6.3.1/docs/contributing.rst000066400000000000000000000001251501302773300176330ustar00rootroot00000000000000------------------ Contributing ------------------ .. include:: ../CONTRIBUTING.rst icalendar-6.3.1/docs/credits.rst000066400000000000000000000061271501302773300165710ustar00rootroot00000000000000icalendar contributors ====================== - Johannes Raggam (Maintainer) - Max M (Original author) - Andreas Zeidler - Andrey Nikolaev - Barak Michener - Christian Geier - Christophe de Vienne - Dai MIKURUBE - Dan Stovall - Eric Hanchrow - Eric Wieser - Erik Simmler - George V. Reilly - Jannis Leidel - Jeroen F.J. Laros - Jeroen van Meeuwen (Kolab Systems) - Jochen Sprickerhof - Jordan Kiang - Klaus Klein - Laurent Lasudry - Lennart Regebro - Léo S - Marc Egli - Markus Unterwaditzer - Martijn Faassen - Martin Melin - Michael Smith - Mikael Frykholm - Olivier Grisel - Pavel Repin - Pedro Ferreira - Rembane - Robert Niederreiter - Rok Garbas - Ronan Dunklau - Russ - Sidnei da Silva - Stanislav Láznička - Stanislav Ochotnicky - Stefan Schwarzer - Thomas Bruederli - Thomas Weißschuh - Victor Varvaryuk - Ville Skyttä - Wichert Akkerman - cillianderoiste - fitnr - hajdbo - ilya - spanktar - tgecho - tisto - TomTry - Andreas Ruppen - Clive Stevens - Dalton Durst - Kamil Mańkowski - Tobias Brox - `Nicco Kunzmann `_ - Robert Spralja - Maurits van Rees - jacadzaca - Mauro Amico - Alexander Pitkin - Michał Górny - Pronoy - Abe Hanoka - `Natasha Mattson `_ - `NikEasY `_ - Matt Lewis - Felix Stupp - Bastian Wegge - `Steve Piercy `_ - Jeffrey Whewhetu - `Soham Dutta `_ - `Serif OZ `_ - David Venhoff - `Tariq `_ Find out who contributed:: $ git shortlog -s -e icalendar-6.3.1/docs/index.rst000066400000000000000000000003631501302773300162370ustar00rootroot00000000000000.. include:: ../README.rst Contents ======== .. toctree:: :maxdepth: 2 about install usage api cli credits maintenance license changelog .. toctree:: :titlesonly: contributing security icalendar-6.3.1/docs/install.rst000066400000000000000000000101251501302773300165730ustar00rootroot00000000000000Installing iCalendar ==================== You can install ``icalendar`` in several ways. Python Package with ``pip`` --------------------------- To install the icalendar package, use: .. code-block:: shell pip install icalendar If installation is successful, you will be able to import the iCalendar package, like this: .. code-block:: pycon >>> import icalendar Debian or Ubuntu ---------------- You can install the `python-icalendar package `_ for Debian or its derivatives. .. code-block:: shell sudo apt-get install python3-icalendar Development Setup ----------------- To start contributing changes to icalendar, you can clone the project to your file system using Git. You can `fork `_ the project first and clone your fork, too. .. code-block:: shell git clone https://github.com/collective/icalendar.git cd icalendar Installing Python ----------------- You will need a version of Python installed to run the tests and execute the code. The latest version of Python 3 should work and will be enough to get you started. If you like to run the tests with different Python versions, the following setup process should work the same. Install Tox ----------- First, install `tox `_.. .. code-block:: shell pip install tox From now on, tox will manage Python versions and test commands for you. Running Tests ------------- ``tox`` manages all test environments in all Python versions. To run all tests in all environments, simply run ``tox`` .. code-block:: shell tox You may not have all Python versions installed or you may want to run a specific one. Have a look at the `documentation `_. This is how you can run ``tox`` with Python 3.9: .. code-block:: shell tox -e py39 Code Style ---------- We strive towards a common code style. You can run the following command to auto-format the code. .. code-block:: shell tox -e ruff Accessing a ``tox`` environment ------------------------------- If you like to enter a specific tox environment, you can do this: .. code-block:: shell source .tox/py39/bin/activate Install ``icalendar`` Manually ------------------------------- The best way to test the package is to use ``tox`` as described above. If for some reason you cannot install ``tox``, you can go ahead with the following section using your installed version of Python and ``pip``. If for example, you would like to use your local copy of icalendar in another Python environment, this section explains how to do it. You can install the local copy of ``icalendar`` with ``pip`` like this: .. code-block:: shell cd icalendar python -m pip install -e . This installs the module and dependencies in your Python environment so that you can access local changes. If tox fails to install ``icalendar`` during its first run, you can activate the environment in the ``.tox`` folder and manually setup ``icalendar`` like this. Try it out: .. code-block:: pycon Python 3.12.0 (main, Mar 1 2024, 09:09:21) [GCC 13.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import icalendar >>> icalendar.Calendar() VCALENDAR({}) Build the documentation ----------------------- To build the documentation, follow these steps: .. code-block:: shell source .tox/py311/bin/activate pip install -r requirements_docs.txt cd docs make html You can now open the output from ``_build/html/index.html``. To build the documentation, view it in a web browser, and automatically reload changes while you edit documentation, use the following command. .. code-block:: shell make livehtml Then open a web browser at `http://127.0.0.1:8050 `_. To build the presentation-version use the following command. .. code-block:: shell make presentation You can open the presentation at ``presentation/index.html``. You can also use ``tox`` to build the documentation: .. code-block:: shell cd icalendar tox -e docs icalendar-6.3.1/docs/license.rst000066400000000000000000000000341501302773300165450ustar00rootroot00000000000000.. include:: ../LICENSE.rst icalendar-6.3.1/docs/maintenance.rst000066400000000000000000000135131501302773300174130ustar00rootroot00000000000000Maintenance =========== The goal of this section is to make sure that the ``icalendar`` library receives a clear maintenance structure with it that is transparent. Maintainers ----------- Currently, the maintainers are - `@geier `_ - `@jacadzaca `_ - `@niccokunzmann `_ Maintainers need this: - ``Admin`` access to the `repository `_. These can be enabled by a current maintainer or an GitHub organisation administrator in the `settings `_. - ``Maintainer`` or ``Owner`` access to the `PyPI project `_. The new maintainer needs a PyPI account for this with Two Factor Authentication (2FA) enabled because ``icalendar`` is a critical project on PyPI. The access can be given in the `collaboration Section `_ on PyPI. - ``Maintainer`` access to the `Read The Docs project `_. This can be given by existing maintainers listed on the project's page. TODO: link to the settings - ``PyPI environment access for GitHub Actions`` grant new releases from tags. This access can be granted in `Settings → Environments → PyPI `__ by adding the GitHub username to the list of "Required Reviewers". Collaborators ------------- Collaborators are people with write access to the repository. As a collaborator, you can - merge Pull Requests, - initiate a new release, see below. We want to have as many knowledgeable people with write access taking responsibility as possible for these reasons: - a constant flow of fixes and new features - better code review - lower bus factor and backup - future maintainers Nobody should merge their own pull requests. If you like to review or merge pull requests of other people and you have shown that you know how to contribute, you can ask for becoming a collaborator or a maintainer asks you if you would like to become one. New Releases ------------ This explains how to create a new release on `PyPI `_. Since collaborators and maintainers have write access the the repository, they can start the release process. However, only people with ``PyPI environment access for GitHub Actions`` can approve an automated release to PyPI. 1. Check that the ``CHANGES.rst`` is up to date with the `latest merged pull requests `__ and the version you want to release is correctly named. .. code-block:: bash export VERSION=6.3.0 2. Create a commit on the ``release`` branch (or equivalent) to release this version. .. code-block:: bash git checkout main git pull git checkout -b release main git add CHANGES.rst git commit -m"version $VERSION" 3. Push the commit and `create a pull request `__ Here is an `example pull request #457 `__. .. code-block:: bash git push -u origin release 4. See if the `CI-tests `_ are running on the pull request. If they are not running, no new release can be issued. If the tests are running, merge the pull request. 5. Clean up behind you! .. code-block:: bash git checkout main git pull git branch -d release git push -d origin release 6. Create a tag for the release and see if the `CI-tests`_ are running. .. code-block:: bash git checkout main git pull git tag "v$VERSION" git push upstream "v$VERSION" # could be origin or whatever reference 7. Once the tag is pushed and its `CI-tests`_ are passing, maintainers will get an e-mail:: Subject: Deployment review in collective/icalendar tests: PyPI is waiting for your review 8. If the release is approved by a maintainer. It will be pushed to `PyPI`_. If that happens, notify the issues that were fixed about this release. 9. Copy this to the start of ``CHANGES.rst``:: 6.3.1 (unreleased) ------------------ Minor changes: - ... Breaking changes: - ... New features: - ... Bug fixes: - ... 10. Push the new CHANGELOG so it is used for future changes. .. code-block:: bash git checkout main git pull git add CHANGES.rst git commit -m"Add new CHANGELOG section for future release See https://icalendar.readthedocs.io/en/latest/maintenance.html#new-releases" git push upstream main # could be origin or whatever reference Links ----- This section contains useful links for maintainers and collaborators: - `Future of icalendar, looking for maintainer #360 `__ - `Comment on the Plone tests running with icalendar `__ Updating Python Versions ------------------------ When adding support for a new Python version or removing support for an old one, the following files need to be updated: 1. ``.github/workflows/tests.yml``: Add or remove the Python version from the test matrix. 2. ``tox.ini``: Update the ``envlist`` to include or remove the Python version. 3. ``pyproject.toml``: Update the ``requires-python`` line and the ``classifiers`` list. 4. ``README.rst``: Update the compatibility information. 5. ``docs/maintenance.rst``: Update this list if any new files need to be modified. Remember to test the changes thoroughly and update any documentation that mentions supported Python versions. icalendar-6.3.1/docs/security.rst000066400000000000000000000053511501302773300170010ustar00rootroot00000000000000Security Policy =============== This documents the security policy and actions to take to secure the package and its deployment and use. Supported Versions ------------------ Security vulnerabilities are fixed only for the latest version of ``icalendar``. .. list-table:: Versions to receive security updates :widths: 25 25 :header-rows: 1 * - Version - Supported * - 6.* - YES * - 5.* - no * - 4.* - no * - before 4.* - no Reporting a Vulnerability ------------------------- To report security issues of ``collective/icalendar``, use the ``Report a vulnerability`` button on the project's `Security Page `_. If you cannot do this, please contact one of the :ref:`maintainers` directly. The maintainers of ``icalendar`` will then notify `Plone's security team `_. If we determine that your report may be a security issue with the project, we may contact you for further information. We volunteers ask that you delay public disclosure of your report for at least ninety (90) days from the date you report it to us. This will allow sufficient time for us to process your report and coordinate disclosure with you. Once verified and fixed, the following steps will be taken: - We will use GitHub's Security Advisory tool to report the issue. - GitHub will review our Security Advisory report for compliance with Common Vulnerabilities and Exposures (CVE) rules. If it is compliant, they will submit it to the MITRE Corporation to generate a `CVE `_. This in turn submits the CVE to the `National Vulnerability Database (NVD) `_. GitHub notifies us of their decision. - Assuming it is compliant, we then publish `our Security Advisory `_ on GitHub, which triggers the next steps. - GitHub will publish the CVE to the CVE List. - GitHub will broadcast our Security Advisory via the `GitHub Advisory Database `_. - GitHub will send `security alerts `_ to all repositories that use our package (and have opted into security alerts). This includes Dependabot alerts. - We will make a bug-fix release. - We will send an announcement through our usual channels: - The :ref:`Changelog` - The GitHub releases of ``icalendar`` - If possible also `Plone's Security Announcements `_ - We will provide credit to the reporter or researcher in the vulnerability notice. icalendar-6.3.1/docs/usage.rst000066400000000000000000000320641501302773300162370ustar00rootroot00000000000000iCalendar package ================= This package is used for parsing and generating iCalendar files following the standard in RFC 5545. It should be fully compliant, but it is possible to generate and parse invalid files if you really want to. Compatibility ------------- This package is compatible with the following standards: - :rfc:`2445` - obsoleted by :rfc:`5545` - :rfc:`5545` - Internet Calendaring and Scheduling Core Object Specification (iCalendar) - :rfc:`6868` - Parameter Value Encoding in iCalendar and vCard - :rfc:`7529` - Non-Gregorian Recurrence Rules in the Internet Calendaring and Scheduling Core Object Specification (iCalendar) - :rfc:`9074` - "VALARM" Extensions for iCalendar We do not claim compatibility to the following RFCs. They might work though. - :rfc:`7953` - Calendar Availability - :rfc:`7986` - New Properties for iCalendar - :rfc:`9073` - Event Publishing Extensions to iCalendar - :rfc:`9253` - Support for iCalendar Relationships iCalendar file structure ------------------------ An iCalendar file is a text file with UTF-8 character encoding in a special format. It consists of **content lines**, with each content line defining a property that has 3 parts: name, parameters, and values. Parameters are optional. Example 1: a simple content line, with only name and value. .. code-block:: text BEGIN:VCALENDAR Example 2: a content line with parameters. .. code-block:: text ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:example@example.com The parts in this example are the following. .. code-block:: text Name: ATTENDEE Params: CN=Max Rasmussen;ROLE=REQ-PARTICIPANT Value: MAILTO:example@example.com For long content lines, iCalendar usually "folds" them to less than 75 characters. On a higher level, you can think of iCalendar files' structure as having components and subcomponents. A component will have properties with values. The values have special types, like integer, text, and datetime. These values are encoded in a special text format in an iCalendar file. This package contains methods for converting to and from these encodings. Example 1: this is a VCALENDAR component representing a calendar. .. code-block:: text BEGIN:VCALENDAR ... vcalendar properties ... END:VCALENDAR Example 2: The most frequent subcomponent to a VCALENDAR component is a VEVENT. This is a VCALENDAR component with a nested VEVENT subcomponent. .. code-block:: text BEGIN:VCALENDAR ... vcalendar properties ... BEGIN:VEVENT ... vevent properties ... END:VEVENT END:VCALENDAR Components ---------- The remaining code snippets in the documentation will use the following important imports. .. code-block:: pycon >>> from icalendar import Calendar, Event Components are like (Case Insensitive) dicts. So if you want to set a property you do it like this. The calendar is a component. .. code-block:: pycon >>> cal = Calendar() >>> cal['dtstart'] = '20050404T080000' >>> cal['summary'] = 'Python meeting about calendaring' >>> for k,v in cal.items(): ... k,v ('DTSTART', '20050404T080000') ('SUMMARY', 'Python meeting about calendaring') NOTE: the recommended way to add components to the calendar is to 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. .. code-block:: pycon >>> cal.to_ical() b'BEGIN:VCALENDAR\r\nDTSTART:20050404T080000\r\nSUMMARY:Python meeting about calendaring\r\nEND:VCALENDAR\r\n' The rendered view is easier to read. .. code-block:: pycon 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. .. code-block:: pycon >>> def display(cal): ... return cal.to_ical().decode("utf-8").replace('\r\n', '\n').strip() You can set multiple properties like this. .. code-block:: pycon >>> 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. .. code-block:: pycon >>> 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 5545 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. .. code-block:: pycon >>> event = Event() >>> event['uid'] = '42' >>> event['dtstart'] = '20050404T080000' And then appending it to a "parent". .. code-block:: pycon >>> 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. .. code-block:: pycon >>> cal.subcomponents [VEVENT({'UID': '42', 'DTSTART': '20050404T080000'})] Value types ----------- Property values are utf-8 encoded strings. This is impractical if you want to use the data for further computation. The datetime format for example 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. .. code-block:: pycon >>> from datetime import datetime >>> cal.add('dtstart', datetime(2005,4,4,8,0,0)) >>> cal['dtstart'].to_ical() b'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. .. code-block:: pycon >>> from icalendar import vDatetime >>> now = datetime(2005,4,4,8,0,0) >>> vDatetime(now).to_ical() b'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. .. code-block:: pycon >>> vDatetime.from_ical('20050404T080000') datetime.datetime(2005, 4, 4, 8, 0) >>> vDatetime.from_ical('20050404T080000Z') datetime.datetime(2005, 4, 4, 8, 0, tzinfo=ZoneInfo(key='UTC')) You can also choose to use the decoded() method, which will return a decoded value directly. .. code-block:: pycon >>> cal = Calendar() >>> cal.add('dtstart', datetime(2005,4,4,8,0,0)) >>> cal['dtstart'].to_ical() b'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. .. code-block:: pycon >>> import zoneinfo >>> event = Event() >>> event.add('dtstart', datetime(2010, 10, 10, 10, 0, 0, ... tzinfo=zoneinfo.ZoneInfo("Europe/Vienna"))) >>> lines = event.to_ical().splitlines() >>> assert ( ... b"DTSTART;TZID=Europe/Vienna:20101010T100000" ... in lines) You can also add arbitrary property parameters by passing a parameters dictionary to the add method like so. .. code-block:: pycon >>> event = Event() >>> event.add('X-TEST-PROP', 'tryout.', ... parameters={'prop1':'val1', 'prop2':'val2'}) >>> lines = event.to_ical().splitlines() >>> assert 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. Initialize the calendar. .. code-block:: pycon >>> cal = Calendar() >>> from datetime import datetime >>> import zoneinfo Some properties are required to be compliant. .. code-block:: pycon >>> 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. .. code-block:: pycon >>> event = Event() >>> event.add('summary', 'Python meeting about calendaring') >>> event.add('dtstart', datetime(2005,4,4,8,0,0,tzinfo=zoneinfo.ZoneInfo("UTC"))) >>> event.add('dtend', datetime(2005,4,4,10,0,0,tzinfo=zoneinfo.ZoneInfo("UTC"))) >>> event.add('dtstamp', datetime(2005,4,4,0,10,0,tzinfo=zoneinfo.ZoneInfo("UTC"))) A property with parameters. Notice that they are an attribute on the value. .. code-block:: pycon >>> 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``). .. code-block:: pycon >>> 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. .. code-block:: pycon >>> cal.add_component(event) By extending the event with subcomponents, you can create multiple alarms. .. code-block:: pycon >>> from icalendar import Alarm >>> from datetime import timedelta >>> alarm_1h_before = Alarm() >>> alarm_1h_before.add('action', 'DISPLAY') >>> alarm_1h_before.add('trigger', timedelta(hours=-1)) >>> alarm_1h_before.add('description', 'Reminder: Event in 1 hour') >>> event.add_component(alarm_1h_before) >>> alarm_24h_before = Alarm() >>> alarm_24h_before.add('action', 'DISPLAY') >>> alarm_24h_before.add('trigger', timedelta(hours=-24)) >>> alarm_24h_before.add('description', 'Reminder: Event in 24 hours') >>> event.add_component(alarm_24h_before) Or even recurrence, either from a dictionary or a string. Note that if you want to add the reccurence rule from a string, you must use the ``vRecur`` property. Otherwise the rule will be escaped, making it invalid. .. code-block:: pycon >>> event.add('rrule', {'freq': 'daily'}) Write to disk. .. code-block:: pycon >>> import tempfile, os >>> directory = tempfile.mkdtemp() >>> f = open(os.path.join(directory, 'example.ics'), 'wb') >>> f.write(cal.to_ical()) 733 >>> f.close() Print out the calendar. .. code-block:: pycon >>> print(cal.to_ical().decode('utf-8')) # doctest: +NORMALIZE_WHITESPACE BEGIN:VCALENDAR VERSION:2.0 PRODID:-//My calendar product//mxm.dk// BEGIN:VEVENT SUMMARY:Python meeting about calendaring DTSTART:20050404T080000Z DTEND:20050404T100000Z DTSTAMP:20050404T001000Z UID:20050115T101010/27346262376@mxm.dk RRULE:FREQ=DAILY ATTENDEE;CN="Max Rasmussen";ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com ATTENDEE;CN="The Dude";ROLE=REQ-PARTICIPANT:MAILTO:the-dude@example.com LOCATION:Odense\, Denmark ORGANIZER;CN="Max Rasmussen";ROLE=CHAIR:MAILTO:noone@example.com PRIORITY:5 BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Reminder: Event in 1 hour TRIGGER:-PT1H END:VALARM BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Reminder: Event in 24 hours TRIGGER:-P1D END:VALARM END:VEVENT END:VCALENDAR 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-6.3.1/pyproject.toml000066400000000000000000000153061501302773300163650ustar00rootroot00000000000000# # We use the pyproject.toml package specification. # See https://packaging.python.org/en/latest/guides/section-build-and-publish/ # See https://github.com/collective/icalendar/issues/686 # See https://packaging.python.org/en/latest/specifications/pyproject-toml/ # [build-system] requires = ["hatchling>=1.27.0", "hatch-vcs"] build-backend = "hatchling.build" [project] name = "icalendar" license = "BSD-2-Clause" license-files = ["LICENSE.rst"] keywords = [ "calendar", "calendaring", "ical", "icalendar", "event", "todo", "journal", "recurring", "rfc5545", ] # This email is not in use any more. If you find a better one, go ahead! # See https://github.com/collective/icalendar/pull/707#discussion_r1775275335 authors = [ { name="Plone Foundation", email="plone-developers@lists.sourceforge.net" }, ] maintainers = [ { name="Nicco Kunzmann", email="niccokunzmann@rambler.ru" }, { name="Christian Geier" }, { name="Jaca", email="vitouejj@gmail.com" }, ] # These attributes are dynamically generated by hatch-vcs dynamic = [ "urls", "version" ] description = "iCalendar parser/generator" readme = { file = "README.rst", content-type = "text/x-rst" } # # When adjusting the Python Version, adjust also: # - .github/workflows/tests.yml # - the classifiers below # - the documentation # - the README file # - dependencies for 3.8, Python 3.13 will come our October, too # - tool.ruff.target-version # requires-python = ">=3.8" # see https://pypi.python.org/pypi?%3Aaction=list_classifiers classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ "python-dateutil", # install requirements depending on python version, TODO: Remove with Python 3.8 # see https://www.python.org/dev/peps/pep-0508/#environment-markers "backports.zoneinfo; python_version < '3.9'", "tzdata" ] [project.optional-dependencies] test = [ "pytest", "coverage", "hypothesis", "pytz", ] [project.scripts] icalendar = "icalendar.cli:main" [tool.hatch.build] exclude = [ "/.*", "/*.*", "/src/icalendar/fuzzing", "/dist", "/build", "/htmlcov", ] [tool.hatch.metadata.hooks.vcs.urls] # This is a dynamic generation of [project.urls] Homepage = "https://icalendar.readthedocs.io/" Repository = "https://github.com/collective/icalendar/" source_archive = "https://github.com/collective/icalendar/archive/{commit_hash}.zip" Issues = "https://github.com/collective/icalendar/issues" Documentation = "https://icalendar.readthedocs.io/" Changelog = "https://icalendar.readthedocs.io/en/latest/changelog.html" [tool.hatch.version] # This configuration allows us to use the version from the tags and dynamically generate # version files. This speeds up the release process. source = "vcs" [tool.hatch.version.raw-options] # see https://github.com/ofek/hatch-vcs/issues/43#issuecomment-1553065222 local_scheme = "no-local-version" [tool.hatch.build.hooks.vcs] version-file = "src/icalendar/_version.py" [tool.hatch.metadata] allow-direct-references = true [tool.ruff] target-version = "py38" [tool.ruff.lint] select = ["ALL"] ignore = [ "ANN", # flake8-annotations "B020", # Loop control variable {name} overrides iterable it iterates "C401", # Unnecessary generator (rewrite as a set comprehension) "C901", # {name} is too complex ({complexity} > {max_complexity}) "COM812", # Trailing comma missing "D1", # Missing docstring "D2", # docstrings stuffs "D4", # docstrings stuffs "EM10", # Exception string usage "ERA001", # Found commented-out code "FBT002", # Boolean default positional argument in function definition "FIX", # TODO comments "ISC001", # Implicitly concatenated string literals on one line (to avoid with formatter) "N818", # Exception name {name} should be named with an Error suffix "PLR091", # Too many things (complexity, arguments, branches, etc...) "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable "RUF012", # Mutable class attributes should be annotated with typing.ClassVar "RUF015", # Prefer next({iterable}) over single element slice "S101", # Use of assert detected "TD", # TODO comments "TRY003", # Avoid specifying long messages outside the exception class "S104", # Possible binding to all interfaces "E722", # Do not use bare `except` "RUF005", # Consider iterable unpacking instead of concatenation "DTZ005", # `datetime.datetime.now()` called without a `tz` argument "PERF401", # Use a list comprehension to create a transformed list "ARG002", # Unused method argument: ... "ARG001", # Unused function argument: ... "UP007", # Optional -> X | None remove when migrated to py39+ ] extend-safe-fixes = [ "PT006", # Wrong type passed to first argument of @pytest.mark.parametrize; expected {expected_string} ] [tool.ruff.lint.per-file-ignores] "src/icalendar/tests/*" = [ "B011", # Do not assert False (python -O removes these calls), raise AssertionError() "DTZ001", # datetime.datetime() called without a tzinfo argument "E501", # Indentation is not a multiple of {indent_size} "N802", # Function name {name} should be lowercase "PT011", # pytest.raises({exception}) is too broad, set the match parameter or use a more specific exception "PT012", # pytest.raises() block should contain a single simple statement "PT015", # Assertion always fails, replace with pytest.fail() "T201", # print found "T203", # `pprint` found "RUF001", # String contains ambiguous character ] [tool.pytest.ini_options] # see https://docs.pytest.org/en/6.2.x/customize.html minversion = "6.0" # see https://docs.pytest.org/en/6.2.x/reference.html?highlight=testpaths#confval-testpaths testpaths = [ "src/icalendar/tests", ] # see https://docs.pytest.org/en/6.2.x/reference.html?highlight=testpaths#confval-norecursedirs norecursedirs = [ "src/icalendar/tests/hypothesis", "build", ] filterwarnings = [ "ignore::DeprecationWarning", ] [dependency-groups] dev = [ "pydata-sphinx-theme>=0.14.4", "sphinx-autobuild>=2021.3.14", "sphinx-copybutton>=0.5.2", ] icalendar-6.3.1/requirements_docs.txt000066400000000000000000000001031501302773300177320ustar00rootroot00000000000000Sphinx>=7 pydata-sphinx-theme sphinx-autobuild sphinx-copybutton . icalendar-6.3.1/src/000077500000000000000000000000001501302773300142335ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/000077500000000000000000000000001501302773300161555ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/__init__.py000066400000000000000000000044021501302773300202660ustar00rootroot00000000000000from icalendar.alarms import ( Alarms, AlarmTime, ) from icalendar.cal import ( Alarm, Calendar, Component, ComponentFactory, Event, FreeBusy, Journal, Timezone, TimezoneDaylight, TimezoneStandard, Todo, ) from icalendar.enums import CUTYPE, FBTYPE, PARTSTAT, RANGE, RELATED, RELTYPE, ROLE from icalendar.error import ( ComponentEndMissing, ComponentStartMissing, FeatureWillBeRemovedInFutureVersion, IncompleteAlarmInformation, IncompleteComponent, InvalidCalendar, LocalTimezoneMissing, ) # Parameters and helper methods for splitting and joining string with escaped # chars. from icalendar.parser import ( Parameters, q_join, q_split, ) # Property Data Value Types from icalendar.prop import ( TypesFactory, vBinary, vBoolean, vCalAddress, vDate, vDatetime, vDDDLists, vDDDTypes, vDuration, vFloat, vFrequency, vGeo, vInt, vMonth, vPeriod, vRecur, vSkip, vText, vTime, vUri, vUTCOffset, vWeekday, ) # Switching the timezone provider from icalendar.timezone import use_pytz, use_zoneinfo from .version import __version__, __version_tuple__, version, version_tuple __all__ = [ "Calendar", "Event", "Todo", "Journal", "Timezone", "TimezoneStandard", "TimezoneDaylight", "FreeBusy", "Alarm", "ComponentFactory", "vBinary", "vBoolean", "vCalAddress", "vDatetime", "vDate", "vDDDLists", "vDDDTypes", "vDuration", "vFloat", "vInt", "vPeriod", "vWeekday", "vFrequency", "vRecur", "vText", "vTime", "vUri", "vGeo", "vUTCOffset", "Parameters", "q_split", "q_join", "use_pytz", "use_zoneinfo", "__version__", "version", "__version_tuple__", "version_tuple", "TypesFactory", "Component", "vMonth", "IncompleteComponent", "InvalidCalendar", "Alarms", "AlarmTime", "ComponentEndMissing", "ComponentStartMissing", "IncompleteAlarmInformation", "LocalTimezoneMissing", "CUTYPE", "FBTYPE", "PARTSTAT", "RANGE", "vSkip", "RELATED", "vSkip", "RELTYPE", "ROLE", "FeatureWillBeRemovedInFutureVersion" ] icalendar-6.3.1/src/icalendar/alarms.py000066400000000000000000000324541501302773300200160ustar00rootroot00000000000000"""Compute the times and states of alarms. This takes different calendar software into account and the RFC 9074 (Alarm Extension). - RFC 9074 defines an ACKNOWLEDGED property in the VALARM. - Outlook does not export VALARM information. - Google Calendar uses the DTSTAMP to acknowledge the alarms. - Thunderbird snoozes the alarms with a X-MOZ-SNOOZE-TIME attribute in the event. - Thunderbird acknowledges the alarms with a X-MOZ-LASTACK attribute in the event. - Etar deletes alarms that are acknowledged. - Nextcloud's Webinterface does not do anything with the alarms when the time passes. """ from __future__ import annotations from datetime import date, timedelta, tzinfo from typing import TYPE_CHECKING, Generator, Optional, Union from icalendar.cal import Alarm, Event, Todo from icalendar.error import ( ComponentEndMissing, ComponentStartMissing, IncompleteAlarmInformation, LocalTimezoneMissing, ) from icalendar.timezone import tzp from icalendar.tools import is_date, normalize_pytz, to_datetime if TYPE_CHECKING: from datetime import datetime Parent = Union[Event, Todo] class AlarmTime: """An alarm time with all the information.""" def __init__( self, alarm: Alarm, trigger: datetime, acknowledged_until: Optional[datetime] = None, snoozed_until: Optional[datetime] = None, parent: Optional[Parent] = None, ): """Create a new AlarmTime. alarm the Alarm component trigger a date or datetime at which to trigger the alarm acknowledged_until an optional datetime in UTC until when all alarms have been acknowledged snoozed_until an optional datetime in UTC until which all alarms of the same parent are snoozed parent the optional parent component the alarm refers to local_tzinfo the local timezone that events without tzinfo should have """ self._alarm = alarm self._parent = parent self._trigger = trigger self._last_ack = acknowledged_until self._snooze_until = snoozed_until @property def acknowledged(self) -> Optional[datetime]: """The time in UTC at which this alarm was last acknowledged. If the alarm was not acknowledged (dismissed), then this is None. """ ack = self.alarm.ACKNOWLEDGED if ack is None: return self._last_ack if self._last_ack is None: return ack return max(ack, self._last_ack) @property def alarm(self) -> Alarm: """The alarm component.""" return self._alarm @property def parent(self) -> Optional[Parent]: """This is the component that contains the alarm. This is None if you did not use Alarms.set_component(). """ return self._parent def is_active(self) -> bool: """Whether this alarm is active (True) or acknowledged (False). For example, in some calendar software, this is True until the user looks at the alarm message and clicked the dismiss button. Alarms can be in local time (without a timezone). To calculate if the alarm really happened, we need it to be in a timezone. If a timezone is required but not given, we throw an IncompleteAlarmInformation. """ acknowledged = self.acknowledged if not acknowledged: # if nothing is acknowledged, this alarm counts return True if self._snooze_until is not None and self._snooze_until > acknowledged: return True trigger = self.trigger if trigger.tzinfo is None: raise LocalTimezoneMissing( "A local timezone is required to check if the alarm is still active. " "Use Alarms.set_local_timezone()." ) return trigger > acknowledged @property def trigger(self) -> date: """This is the time to trigger the alarm. If the alarm has been snoozed, this can differ from the TRIGGER property. """ if self._snooze_until is not None and self._snooze_until > self._trigger: return self._snooze_until return self._trigger class Alarms: """Compute the times and states of alarms. This is an example using RFC 9074. One alarm is 30 minutes before the event and acknowledged. Another alarm is 15 minutes before the event and still active. >>> from icalendar import Event, Alarms >>> event = Event.from_ical( ... '''BEGIN:VEVENT ... CREATED:20210301T151004Z ... UID:AC67C078-CED3-4BF5-9726-832C3749F627 ... DTSTAMP:20210301T151004Z ... DTSTART;TZID=America/New_York:20210302T103000 ... DTEND;TZID=America/New_York:20210302T113000 ... SUMMARY:Meeting ... BEGIN:VALARM ... UID:8297C37D-BA2D-4476-91AE-C1EAA364F8E1 ... TRIGGER:-PT30M ... ACKNOWLEDGED:20210302T150004Z ... DESCRIPTION:Event reminder ... ACTION:DISPLAY ... END:VALARM ... BEGIN:VALARM ... UID:8297C37D-BA2D-4476-91AE-C1EAA364F8E1 ... TRIGGER:-PT15M ... DESCRIPTION:Event reminder ... ACTION:DISPLAY ... END:VALARM ... END:VEVENT ... ''') >>> alarms = Alarms(event) >>> len(alarms.times) # all alarms including those acknowledged 2 >>> len(alarms.active) # the alarms that are not acknowledged, yet 1 >>> alarms.active[0].trigger # this alarm triggers 15 minutes before 10:30 datetime.datetime(2021, 3, 2, 10, 15, tzinfo=ZoneInfo(key='America/New_York')) RFC 9074 specifies that alarms can also be triggered by proximity. This is not implemented yet. """ def __init__(self, component: Optional[Alarm | Event | Todo] = None): """Start computing alarm times.""" self._absolute_alarms: list[Alarm] = [] self._start_alarms: list[Alarm] = [] self._end_alarms: list[Alarm] = [] self._start: Optional[date] = None self._end: Optional[date] = None self._parent: Optional[Parent] = None self._last_ack: Optional[datetime] = None self._snooze_until: Optional[datetime] = None self._local_tzinfo: Optional[tzinfo] = None if component is not None: self.add_component(component) def add_component(self, component: Alarm | Parent): """Add a component. If this is an alarm, it is added. Events and Todos are added as a parent and all their alarms are added, too. """ if isinstance(component, (Event, Todo)): self.set_parent(component) self.set_start(component.start) self.set_end(component.end) if component.is_thunderbird(): self.acknowledge_until(component.X_MOZ_LASTACK) self.snooze_until(component.X_MOZ_SNOOZE_TIME) else: self.acknowledge_until(component.DTSTAMP) for alarm in component.walk("VALARM"): self.add_alarm(alarm) def set_parent(self, parent: Parent): """Set the parent of all the alarms. If you would like to collect alarms from a component, use add_component """ if self._parent is not None and self._parent is not parent: raise ValueError("You can only set one parent for this alarm calculation.") self._parent = parent def add_alarm(self, alarm: Alarm) -> None: """Optional: Add an alarm component.""" trigger = alarm.TRIGGER if trigger is None: return if isinstance(trigger, date): self._absolute_alarms.append(alarm) elif alarm.TRIGGER_RELATED == "START": self._start_alarms.append(alarm) else: self._end_alarms.append(alarm) def set_start(self, dt: Optional[date]): """Set the start of the component. If you have only absolute alarms, this is not required. If you have alarms relative to the start of a compoment, set the start here. """ self._start = dt def set_end(self, dt: Optional[date]): """Set the end of the component. If you have only absolute alarms, this is not required. If you have alarms relative to the end of a compoment, set the end here. """ self._end = dt def _add(self, dt: date, td: timedelta): """Add a timedelta to a datetime.""" if is_date(dt): if td.seconds == 0: return dt + td dt = to_datetime(dt) return normalize_pytz(dt + td) def acknowledge_until(self, dt: Optional[date]) -> None: """This is the time in UTC when all the alarms of this component were acknowledged. Only the last call counts. Since RFC 9074 (Alarm Extension) was created later, calendar implementations differ in how they acknowledge alarms. For example, Thunderbird and Google Calendar store the last time an event has been acknowledged because of an alarm. All alarms that happen before this time count as acknowledged. """ self._last_ack = tzp.localize_utc(dt) if dt is not None else None def snooze_until(self, dt: Optional[date]) -> None: """This is the time in UTC when all the alarms of this component were snoozed. Only the last call counts. The alarms are supposed to turn up again at dt when they are not acknowledged but snoozed. """ self._snooze_until = tzp.localize_utc(dt) if dt is not None else None def set_local_timezone(self, tzinfo: Optional[tzinfo | str]): """Set the local timezone. Events are sometimes in local time. In order to compute the exact time of the alarm, some alarms without timezone are considered local. Some computations work without setting this, others don't. If they need this information, expect a LocalTimezoneMissing exception somewhere down the line. """ self._local_tzinfo = tzp.timezone(tzinfo) if isinstance(tzinfo, str) else tzinfo @property def times(self) -> list[AlarmTime]: """Compute and return the times of the alarms given. If the information for calculation is incomplete, this will raise a IncompleteAlarmInformation exception. Please make sure to set all the required parameters before calculating. If you forget to set the acknowledged times, that is not problem. """ return ( self._get_end_alarm_times() + self._get_start_alarm_times() + self._get_absolute_alarm_times() ) def _repeat(self, first: datetime, alarm: Alarm) -> Generator[datetime]: """The times when the alarm is triggered relative to start.""" yield first # we trigger at the start repeat = alarm.REPEAT duration = alarm.DURATION if repeat and duration: for i in range(1, repeat + 1): yield self._add(first, duration * i) def _alarm_time(self, alarm: Alarm, trigger: date): """Create an alarm time with the additional attributes.""" if getattr(trigger, "tzinfo", None) is None and self._local_tzinfo is not None: trigger = normalize_pytz(trigger.replace(tzinfo=self._local_tzinfo)) return AlarmTime( alarm, trigger, self._last_ack, self._snooze_until, self._parent ) def _get_absolute_alarm_times(self) -> list[AlarmTime]: """Return a list of absolute alarm times.""" return [ self._alarm_time(alarm, trigger) for alarm in self._absolute_alarms for trigger in self._repeat(alarm.TRIGGER, alarm) ] def _get_start_alarm_times(self) -> list[AlarmTime]: """Return a list of alarm times relative to the start of the component.""" if self._start is None and self._start_alarms: raise ComponentStartMissing( "Use Alarms.set_start because at least one alarm is relative to the start of a component." ) return [ self._alarm_time(alarm, trigger) for alarm in self._start_alarms for trigger in self._repeat(self._add(self._start, alarm.TRIGGER), alarm) ] def _get_end_alarm_times(self) -> list[AlarmTime]: """Return a list of alarm times relative to the start of the component.""" if self._end is None and self._end_alarms: raise ComponentEndMissing( "Use Alarms.set_end because at least one alarm is relative to the end of a component." ) return [ self._alarm_time(alarm, trigger) for alarm in self._end_alarms for trigger in self._repeat(self._add(self._end, alarm.TRIGGER), alarm) ] @property def active(self) -> list[AlarmTime]: """The alarm times that are still active and not acknowledged. This considers snoozed alarms. Alarms can be in local time (without a timezone). To calculate if the alarm really happened, we need it to be in a timezone. If a timezone is required but not given, we throw an IncompleteAlarmInformation. """ return [alarm_time for alarm_time in self.times if alarm_time.is_active()] __all__ = [ "Alarms", "AlarmTime", "IncompleteAlarmInformation", "ComponentEndMissing", "ComponentStartMissing", ] icalendar-6.3.1/src/icalendar/attr.py000066400000000000000000000630141501302773300175050ustar00rootroot00000000000000"""Attributes of Components and properties.""" from __future__ import annotations import itertools from datetime import date, datetime, timedelta from typing import TYPE_CHECKING, Optional, Union from icalendar.error import InvalidCalendar from icalendar.prop import vCategory, vDDDTypes, vRecur, vText from icalendar.timezone import tzp if TYPE_CHECKING: from icalendar.cal import Component def _get_rdates(self: Component) -> list[ Union[tuple[date, None], tuple[datetime, None], tuple[datetime, datetime]]]: """The RDATE property defines the list of DATE-TIME values for recurring components. RDATE is defined in :rfc:`5545`. The return value is a list of tuples ``(start, end)``. ``start`` can be a :class:`datetime.date` or a :class:`datetime.datetime`, with and without timezone. ``end`` is :obj:`None` if the end is not specified and a :class:`datetime.datetime` if the end is specified. Value Type: The default value type for this property is DATE-TIME. The value type can be set to DATE or PERIOD. Property Parameters: IANA, non-standard, value data type, and time zone identifier property parameters can be specified on this property. Conformance: This property can be specified in recurring "VEVENT", "VTODO", and "VJOURNAL" calendar components as well as in the "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE" calendar component. Description: This property can appear along with the "RRULE" property to define an aggregate set of repeating occurrences. When they both appear in a recurring component, the recurrence instances are defined by the union of occurrences defined by both the "RDATE" and "RRULE". The recurrence dates, if specified, are used in computing the recurrence set. The recurrence set is the complete set of recurrence instances for a calendar component. The recurrence set is generated by considering the initial "DTSTART" property along with the "RRULE", "RDATE", and "EXDATE" properties contained within the recurring component. The "DTSTART" property defines the first instance in the recurrence set. The "DTSTART" property value SHOULD match the pattern of the recurrence rule, if specified. The recurrence set generated with a "DTSTART" property value that doesn't match the pattern of the rule is undefined. The final recurrence set is generated by gathering all of the start DATE-TIME values generated by any of the specified "RRULE" and "RDATE" properties, and then excluding any start DATE-TIME values specified by "EXDATE" properties. This implies that start DATE-TIME values specified by "EXDATE" properties take precedence over those specified by inclusion properties (i.e., "RDATE" and "RRULE"). Where duplicate instances are generated by the "RRULE" and "RDATE" properties, only one recurrence is considered. Duplicate instances are ignored. Example: Below, we set one RDATE in a list and get the resulting tuple of start and end. .. code-block:: pycon >>> from icalendar import Event >>> from datetime import datetime >>> event = Event() # Add a list of recurrence dates >>> event.add("RDATE", [datetime(2025, 4, 28, 16, 5)]) >>> event.rdates [(datetime.datetime(2025, 4, 28, 16, 5), None)] .. note:: You cannot modify the RDATE value by modifying the result. Use :func:`icalendar.cal.Component.add` to add values. If you want to compute recurrences, have a look at :ref:`Related projects`. """ result = [] rdates = self.get("RDATE", []) for rdates in (rdates,) if not isinstance(rdates, list) else rdates: for dts in rdates.dts: rdate = dts.dt if isinstance(rdate, tuple): # we have a period as rdate if isinstance(rdate[1], timedelta): result.append((rdate[0], rdate[0] + rdate[1])) else: result.append(rdate) else: # we have a date/datetime result.append((rdate, None)) return result rdates_property = property(_get_rdates) def _get_exdates(self: Component) -> list[date|datetime]: """EXDATE defines the list of DATE-TIME exceptions for recurring components. EXDATE is defined in :rfc:`5545`. Value Type: The default value type for this property is DATE-TIME. The value type can be set to DATE. Property Parameters: IANA, non-standard, value data type, and time zone identifier property parameters can be specified on this property. Conformance: This property can be specified in recurring "VEVENT", "VTODO", and "VJOURNAL" calendar components as well as in the "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE" calendar component. Description: The exception dates, if specified, are used in computing the recurrence set. The recurrence set is the complete set of recurrence instances for a calendar component. The recurrence set is generated by considering the initial "DTSTART" property along with the "RRULE", "RDATE", and "EXDATE" properties contained within the recurring component. The "DTSTART" property defines the first instance in the recurrence set. The "DTSTART" property value SHOULD match the pattern of the recurrence rule, if specified. The recurrence set generated with a "DTSTART" property value that doesn't match the pattern of the rule is undefined. The final recurrence set is generated by gathering all of the start DATE-TIME values generated by any of the specified "RRULE" and "RDATE" properties, and then excluding any start DATE-TIME values specified by "EXDATE" properties. This implies that start DATE-TIME values specified by "EXDATE" properties take precedence over those specified by inclusion properties (i.e., "RDATE" and "RRULE"). When duplicate instances are generated by the "RRULE" and "RDATE" properties, only one recurrence is considered. Duplicate instances are ignored. The "EXDATE" property can be used to exclude the value specified in "DTSTART". However, in such cases, the original "DTSTART" date MUST still be maintained by the calendaring and scheduling system because the original "DTSTART" value has inherent usage dependencies by other properties such as the "RECURRENCE-ID". Example: Below, we add an exdate in a list and get the resulting list of exdates. .. code-block:: pycon >>> from icalendar import Event >>> from datetime import datetime >>> event = Event() # Add a list of excluded dates >>> event.add("EXDATE", [datetime(2025, 4, 28, 16, 5)]) >>> event.exdates [datetime.datetime(2025, 4, 28, 16, 5)] .. note:: You cannot modify the EXDATE value by modifying the result. Use :func:`icalendar.cal.Component.add` to add values. If you want to compute recurrences, have a look at :ref:`Related projects`. """ result = [] exdates = self.get("EXDATE", []) for exdates in (exdates,) if not isinstance(exdates, list) else exdates: for dts in exdates.dts: exdate = dts.dt # we have a date/datetime result.append(exdate) return result exdates_property = property(_get_exdates) def _get_rrules(self: Component) -> list[vRecur]: """RRULE defines a rule or repeating pattern for recurring components. RRULE is defined in :rfc:`5545`. :rfc:`7529` adds the ``SKIP`` parameter :class:`icalendar.prop.vSkip`. Property Parameters: IANA and non-standard property parameters can be specified on this property. Conformance: This property can be specified in recurring "VEVENT", "VTODO", and "VJOURNAL" calendar components as well as in the "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE" calendar component, but it SHOULD NOT be specified more than once. The recurrence set generated with multiple "RRULE" properties is undefined. Description: The recurrence rule, if specified, is used in computing the recurrence set. The recurrence set is the complete set of recurrence instances for a calendar component. The recurrence set is generated by considering the initial "DTSTART" property along with the "RRULE", "RDATE", and "EXDATE" properties contained within the recurring component. The "DTSTART" property defines the first instance in the recurrence set. The "DTSTART" property value SHOULD be synchronized with the recurrence rule, if specified. The recurrence set generated with a "DTSTART" property value not synchronized with the recurrence rule is undefined. The final recurrence set is generated by gathering all of the start DATE-TIME values generated by any of the specified "RRULE" and "RDATE" properties, and then excluding any start DATE-TIME values specified by "EXDATE" properties. This implies that start DATE- TIME values specified by "EXDATE" properties take precedence over those specified by inclusion properties (i.e., "RDATE" and "RRULE"). Where duplicate instances are generated by the "RRULE" and "RDATE" properties, only one recurrence is considered. Duplicate instances are ignored. The "DTSTART" property specified within the iCalendar object defines the first instance of the recurrence. In most cases, a "DTSTART" property of DATE-TIME value type used with a recurrence rule, should be specified as a date with local time and time zone reference to make sure all the recurrence instances start at the same local time regardless of time zone changes. If the duration of the recurring component is specified with the "DTEND" or "DUE" property, then the same exact duration will apply to all the members of the generated recurrence set. Else, if the duration of the recurring component is specified with the "DURATION" property, then the same nominal duration will apply to all the members of the generated recurrence set and the exact duration of each recurrence instance will depend on its specific start time. For example, recurrence instances of a nominal duration of one day will have an exact duration of more or less than 24 hours on a day where a time zone shift occurs. The duration of a specific recurrence may be modified in an exception component or simply by using an "RDATE" property of PERIOD value type. Examples: Daily for 10 occurrences: .. code-block:: pycon >>> from icalendar import Event >>> from datetime import datetime >>> from zoneinfo import ZoneInfo >>> event = Event() >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York")) >>> event.add("RRULE", "FREQ=DAILY;COUNT=10") >>> print(event.to_ical()) BEGIN:VEVENT DTSTART;TZID=America/New_York:19970902T090000 RRULE:FREQ=DAILY;COUNT=10 END:VEVENT >>> event.rrules [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})] Daily until December 24, 1997: .. code-block:: pycon >>> from icalendar import Event, vRecur >>> from datetime import datetime >>> from zoneinfo import ZoneInfo >>> event = Event() >>> event.start = datetime(1997, 9, 2, 9, 0, tzinfo=ZoneInfo("America/New_York")) >>> event.add("RRULE", vRecur({"FREQ": ["DAILY"]}, until=datetime(1997, 12, 24, tzinfo=ZoneInfo("UTC")))) >>> print(event.to_ical()) BEGIN:VEVENT DTSTART;TZID=America/New_York:19970902T090000 RRULE:FREQ=DAILY;UNTIL=19971224T000000Z END:VEVENT >>> event.rrules [vRecur({'FREQ': ['DAILY'], 'UNTIL': [datetime.datetime(1997, 12, 24, 0, 0, tzinfo=ZoneInfo(key='UTC'))]})] .. note:: You cannot modify the RRULE value by modifying the result. Use :func:`icalendar.cal.Component.add` to add values. If you want to compute recurrences, have a look at :ref:`Related projects`. """ rrules = self.get("RRULE", []) if not isinstance(rrules, list): return [rrules] return rrules rrules_property = property(_get_rrules) def multi_language_text_property(main_prop:str, compatibility_prop:str, doc:str) -> property: """This creates a text property. This property can be defined several times with different ``LANGUAGE`` parameters. Args: main_prop (str): The property to set and get, such as ``NAME`` compatibility_prop (str): An old property used before, such as ``X-WR-CALNAME`` doc (str): The documentation string """ def fget(self: Component) -> Optional[str]: """Get the property""" result = self.get(main_prop, self.get(compatibility_prop)) if isinstance(result, list): for item in result: if "LANGUAGE" not in item.params: return item return result def fset(self: Component, value:str): """Set the property.""" fdel(self) self.add(main_prop, value) def fdel(self: Component): """Delete the property.""" self.pop(main_prop, None) self.pop(compatibility_prop, None) return property(fget, fset, fdel, doc) def single_int_property(prop:str, default:int, doc:str) -> property: """Create a property for an int value that exists only once. Args: prop: The name of the property default: The default value doc: The documentation string """ def fget(self: Component) -> int: """Get the property""" try: return int(self.get(prop, default)) except ValueError as e: raise InvalidCalendar(f"{prop} must be an int") from e def fset(self: Component, value:int): """Set the property.""" fdel(self) self.add(prop, value) def fdel(self: Component): """Delete the property.""" self.pop(prop, None) return property(fget, fset, fdel, doc) def single_utc_property(name: str, docs: str) -> property: """Create a property to access a value of datetime in UTC timezone. Args: name: name of the property docs: documentation string """ docs = ( f"""The {name} property. datetime in UTC All values will be converted to a datetime in UTC. """ + docs ) def fget(self: Component) -> Optional[datetime]: """Get the value.""" if name not in self: return None dt = self.get(name) if isinstance(dt, vText): # we might be in an attribute that is not typed value = vDDDTypes.from_ical(dt) else: value = getattr(dt, "dt", None) if value is None or not isinstance(value, date): raise InvalidCalendar(f"{name} must be a datetime in UTC, not {value}") return tzp.localize_utc(value) def fset(self: Component, value: datetime): """Set the value""" if not isinstance(value, date): raise TypeError(f"{name} takes a datetime in UTC, not {value}") fdel(self) self.add(name, tzp.localize_utc(value)) def fdel(self: Component): """Delete the property.""" self.pop(name, None) return property(fget, fset, fdel, doc=docs) def single_string_property(name: str, docs: str, other_name:Optional[str]=None) -> property: """Create a property to access a single string value.""" def fget(self: Component) -> str: """Get the value.""" result = self.get(name, None if other_name is None else self.get(other_name, None)) if result is None or result == []: return "" if isinstance(result, list): return result[0] return result def fset(self: Component, value: str): """Set the value""" fdel(self) self.add(name, value) def fdel(self: Component): """Delete the property.""" self.pop(name, None) if other_name is not None: self.pop(other_name, None) return property(fget, fset, fdel, doc=docs) color_property = single_string_property( "COLOR", """This property specifies a color used for displaying the component. This implements :rfc:`7986` ``COLOR`` property. Property Parameters: IANA and non-standard property parameters can be specified on this property. Conformance: This property can be specified once in an iCalendar object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components. Description: This property specifies a color that clients MAY use when presenting the relevant data to a user. Typically, this would appear as the "background" color of events or tasks. The value is a case-insensitive color name taken from the CSS3 set of names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 `_. Example: ``"turquoise"``, ``"#ffffff"`` .. code-block:: pycon >>> from icalendar import Todo >>> todo = Todo() >>> todo.color = "green" >>> print(todo.to_ical()) BEGIN:VTODO COLOR:green END:VTODO """ ) sequence_property = single_int_property( "SEQUENCE", 0, """This property defines the revision sequence number of the calendar component within a sequence of revisions. Value Type: INTEGER Property Parameters: IANA and non-standard property parameters can be specified on this property. Conformance: The property can be specified in "VEVENT", "VTODO", or "VJOURNAL" calendar component. Description: When a calendar component is created, its sequence number is 0. It is monotonically incremented by the "Organizer's" CUA each time the "Organizer" makes a significant revision to the calendar component. The "Organizer" includes this property in an iCalendar object that it sends to an "Attendee" to specify the current version of the calendar component. The "Attendee" includes this property in an iCalendar object that it sends to the "Organizer" to specify the version of the calendar component to which the "Attendee" is referring. A change to the sequence number is not the mechanism that an "Organizer" uses to request a response from the "Attendees". The "RSVP" parameter on the "ATTENDEE" property is used by the "Organizer" to indicate that a response from the "Attendees" is requested. Recurrence instances of a recurring component MAY have different sequence numbers. Examples: The following is an example of this property for a calendar component that was just created by the "Organizer": .. code-block:: pycon >>> from icalendar import Event >>> event = Event() >>> event.sequence 0 The following is an example of this property for a calendar component that has been revised 10 different times by the "Organizer": .. code-block:: pycon >>> from icalendar import Calendar >>> calendar = Calendar.example("issue_156_RDATE_with_PERIOD_TZID_khal") >>> event = calendar.events[0] >>> event.sequence 10 """ ) def _get_categories(component: Component) -> list[str]: """Get all the categories.""" categories : Optional[vCategory|list[vCategory]] = component.get("CATEGORIES") if isinstance(categories, list): _set_categories(component, list(itertools.chain.from_iterable(cat.cats for cat in categories))) return _get_categories(component) if categories is None: categories = vCategory([]) component.add("CATEGORIES", categories) return categories.cats def _set_categories(component: Component, cats: list[str]) -> None: """Set the categories.""" component["CATEGORIES"] = categories = vCategory(cats) cats.clear() cats.extend(categories.cats) categories.cats = cats def _del_categories(component: Component) -> None: """Delete the categories.""" component.pop("CATEGORIES", None) categories_property = property( _get_categories, _set_categories, _del_categories, """This property defines the categories for a component. Property Parameters: IANA, non-standard, and language property parameters can be specified on this property. Conformance: The property can be specified within "VEVENT", "VTODO", or "VJOURNAL" calendar components. Since :rfc:`7986` it can also be defined on a "VCALENDAR" component. Description: This property is used to specify categories or subtypes of the calendar component. The categories are useful in searching for a calendar component of a particular type and category. Within the "VEVENT", "VTODO", or "VJOURNAL" calendar components, more than one category can be specified as a COMMA-separated list of categories. Example: Below, we add the categories to an event: .. code-block:: pycon >>> from icalendar import Event >>> event = Event() >>> event.categories = ["Work", "Meeting"] >>> print(event.to_ical()) BEGIN:VEVENT CATEGORIES:Work,Meeting END:VEVENT >>> event.categories.append("Lecture") >>> event.categories == ["Work", "Meeting", "Lecture"] True .. note:: At present, we do not take the LANGUAGE parameter into account. """ ) uid_property = single_string_property( "UID", """UID specifies the persistent, globally unique identifier for a component. We recommend using :func:`uuid.uuid4` to generate new values. Returns: The value of the UID property as a string or ``""`` if no value is set. Description: The "UID" itself MUST be a globally unique identifier. The generator of the identifier MUST guarantee that the identifier is unique. This is the method for correlating scheduling messages with the referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component. The full range of calendar components specified by a recurrence set is referenced by referring to just the "UID" property value corresponding to the calendar component. The "RECURRENCE-ID" property allows the reference to an individual instance within the recurrence set. This property is an important method for group-scheduling applications to match requests with later replies, modifications, or deletion requests. Calendaring and scheduling applications MUST generate this property in "VEVENT", "VTODO", and "VJOURNAL" calendar components to assure interoperability with other group- scheduling applications. This identifier is created by the calendar system that generates an iCalendar object. Implementations MUST be able to receive and persist values of at least 255 octets for this property, but they MUST NOT truncate values in the middle of a UTF-8 multi-octet sequence. :rfc:`7986` states that UID can be used, for example, to identify duplicate calendar streams that a client may have been given access to. It can be used in conjunction with the "LAST-MODIFIED" property also specified on the "VCALENDAR" object to identify the most recent version of a calendar. Conformance: :rfc:`5545` states that the "UID" property can be specified on "VEVENT", "VTODO", and "VJOURNAL" calendar components. :rfc:`7986` modifies the definition of the "UID" property to allow it to be defined in an iCalendar object. :rfc:`9074` adds a "UID" property to "VALARM" components to allow a unique identifier to be specified. The value of this property can then be used to refer uniquely to the "VALARM" component. This property can be specified once only. Security: :rfc:`7986` states that UID values MUST NOT include any data that might identify a user, host, domain, or any other security- or privacy-sensitive information. It is RECOMMENDED that calendar user agents now generate "UID" values that are hex-encoded random Universally Unique Identifier (UUID) values as defined in Sections 4.4 and 4.5 of :rfc:`4122`. You can use the :mod:`uuid` module to generate new UUIDs. Compatibility: For Alarms, ``X-ALARMUID`` is also considered. Examples: The following is an example of such a property value: ``5FC53010-1267-4F8E-BC28-1D7AE55A7C99``. Set the UID of a calendar: .. code-block:: pycon >>> from icalendar import Calendar >>> from uuid import uuid4 >>> calendar = Calendar() >>> calendar.uid = uuid4() >>> print(calendar.to_ical()) BEGIN:VCALENDAR UID:d755cef5-2311-46ed-a0e1-6733c9e15c63 END:VCALENDAR """ ) __all__ = [ "categories_property", "color_property", "exdates_property", "multi_language_text_property", "rdates_property", "rrules_property", "sequence_property", "single_int_property", "single_utc_property", "uid_property", ] icalendar-6.3.1/src/icalendar/cal.py000066400000000000000000002246171501302773300173020ustar00rootroot00000000000000"""Calendar is a dictionary like Python object that can render itself as VCAL files according to RFC 5545. These are the defined components. """ from __future__ import annotations import os from collections import defaultdict from datetime import date, datetime, timedelta, tzinfo from typing import TYPE_CHECKING, List, NamedTuple, Optional, Tuple, Union import dateutil.rrule import dateutil.tz from icalendar.attr import ( categories_property, color_property, exdates_property, multi_language_text_property, rdates_property, rrules_property, sequence_property, single_int_property, single_string_property, single_utc_property, uid_property, ) from icalendar.caselessdict import CaselessDict from icalendar.error import IncompleteComponent, InvalidCalendar from icalendar.parser import Contentline, Contentlines, Parameters, q_join, q_split from icalendar.parser_tools import DEFAULT_ENCODING from icalendar.prop import ( TypesFactory, tzid_from_tzinfo, vDDDLists, vDDDTypes, vDuration, vText, vUTCOffset, ) from icalendar.timezone import TZP, tzp from icalendar.tools import is_date, to_datetime if TYPE_CHECKING: from icalendar.alarms import Alarms def get_example(component_directory: str, example_name: str) -> bytes: """Return an example and raise an error if it is absent.""" here = os.path.dirname(__file__) examples = os.path.join(here, "tests", component_directory) if not example_name.endswith(".ics"): example_name = example_name + ".ics" example_file = os.path.join(examples, example_name) if not os.path.isfile(example_file): raise ValueError( f"Example {example_name} for {component_directory} not found. You can use one of {', '.join(os.listdir(examples))}" ) with open(example_file, "rb") as f: return f.read() ###################################### # The component factory class ComponentFactory(CaselessDict): """All components defined in RFC 5545 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.""" super().__init__(*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( { "CATEGORIES": 1, "RESOURCES": 1, "FREEBUSY": 1, } ) _marker = [] class Component(CaselessDict): """Component is the base object for calendar, Event and the other components defined in RFC 5545. Normally you will not use this class directly, but rather one of the subclasses. """ name = None # should 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.""" super().__init__(*args, **kwargs) # set parameters here for properties that use non-default values self.subcomponents = [] # Components can be nested. self.errors = [] # If we ignored exception(s) while # parsing a property, contains error strings # 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-compliance by calling this method. # """ # return name in not_compliant def __bool__(self): """Returns True, CaselessDict would return False if it had no items.""" return True # python 2 compatibility __nonzero__ = __bool__ def is_empty(self): """Returns True if Component has no items or subcomponents, else False.""" return True if not (list(self.values()) + self.subcomponents) else False # noqa ############################# # handling of property values @staticmethod def _encode(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. obj = value else: klass = types_factory.for_property(name) obj = klass(value) if parameters: if not hasattr(obj, "params"): obj.params = Parameters() for key, item in parameters.items(): if item is None: if key in obj.params: del obj.params[key] else: obj.params[key] = item return obj def add(self, name, value, parameters=None, encode=1): """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. value = tzp.localize_utc(value) # encode value if ( encode and isinstance(value, list) and name.lower() not in ["rdate", "exdate", "categories"] ): # 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. 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 separated 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: Component): """Add a subcomponent to this component.""" self.subcomponents.append(component) def _walk(self, name, select): """Walk to given component.""" result = [] if (name is None or self.name == name) and select(self): result.append(self) for subcomponent in self.subcomponents: result += subcomponent._walk(name, select) return result def walk(self, name=None, select=lambda c: True) -> list[Component]: """Recursively traverses component and subcomponents. Returns sequence of same. If name is passed, only components with name will be returned. :param name: The name of the component or None such as ``VEVENT``. :param select: A function that takes the component as first argument and returns True/False. :returns: A list of components that match. :rtype: list[Component] """ if name is not None: name = name.upper() return self._walk(name, select) ##################### # Generation def property_items(self, recursive=True, sorted=True) -> list[tuple[str, object]]: """Returns properties in this component and subcomponents as: [(name, value), ...] """ vText = types_factory["text"] properties = [("BEGIN", vText(self.name).to_ical())] if sorted: property_names = self.sorted_keys() else: property_names = self.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(sorted=sorted) 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 try: name, params, vals = line.parts() except ValueError as e: # if unable to parse a line within a component # that ignores exceptions, mark the component # as broken and skip the line. otherwise raise. component = stack[-1] if stack else None if not component or not component.ignore_exceptions: raise component.errors.append((None, str(e))) continue 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, Component) # If component factory cannot resolve ``c_name``, the generic # ``Component`` class is used which does not have the name set. # That's opposed to the usage of ``cls``, which represents a # more concrete subclass with a name set (e.g. VCALENDAR). 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. if not stack: # The stack is currently empty, the input must be invalid raise ValueError("END encountered without an accompanying BEGIN!") component = stack.pop() if not stack: # we are at the end comps.append(component) else: stack[-1].add_component(component) if vals == "VTIMEZONE" and "TZID" in component: tzp.cache_timezone_component(component) # we are adding properties to the current top of the stack else: factory = types_factory.for_property(name) component = stack[-1] if stack else None if not component: # only accept X-COMMENT at the end of the .ics file # ignore these components in parsing if uname == "X-COMMENT": break else: raise ValueError( f'Property "{name}" does not have a parent component.' ) datetime_names = ( "DTSTART", "DTEND", "RECURRENCE-ID", "DUE", "RDATE", "EXDATE", ) try: if name == "FREEBUSY": vals = vals.split(",") if "TZID" in params: parsed_components = [ factory(factory.from_ical(val, params["TZID"])) for val in vals ] else: parsed_components = [ factory(factory.from_ical(val)) for val in vals ] elif name in datetime_names and "TZID" in params: parsed_components = [ factory(factory.from_ical(vals, params["TZID"])) ] else: parsed_components = [factory(factory.from_ical(vals))] except ValueError as e: if not component.ignore_exceptions: raise component.errors.append((uname, str(e))) else: for parsed_component in parsed_components: parsed_component.params = params component.add(name, parsed_component, encode=0) if multiple: return comps if len(comps) > 1: raise ValueError( cls._format_error( "Found multiple components where only one is allowed", st ) ) if len(comps) < 1: raise ValueError( cls._format_error( "Found no components where exactly one is required", st ) ) return comps[0] @staticmethod def _format_error(error_description, bad_input, elipsis="[...]"): # there's three character more in the error, ie. ' ' x2 and a ':' max_error_length = 100 - 3 if len(error_description) + len(bad_input) + len(elipsis) > max_error_length: truncate_to = max_error_length - len(error_description) - len(elipsis) return f"{error_description}: {bad_input[:truncate_to]} {elipsis}" else: return f"{error_description}: {bad_input}" def content_line(self, name, value, sorted=True): """Returns property as content line.""" params = getattr(value, "params", Parameters()) return Contentline.from_parts(name, params, value, sorted=sorted) def content_lines(self, sorted=True): """Converts the Component and subcomponents into content lines.""" contentlines = Contentlines() for name, value in self.property_items(sorted=sorted): cl = self.content_line(name, value, sorted=sorted) contentlines.append(cl) contentlines.append("") # remember the empty string in the end return contentlines def to_ical(self, sorted=True): """ :param sorted: Whether parameters and properties should be lexicographically sorted. """ content_lines = self.content_lines(sorted=sorted) return content_lines.to_ical() def __repr__(self): """String representation of class with all of it's subcomponents.""" subs = ", ".join(str(it) for it in self.subcomponents) return f"{self.name or type(self).__name__}({dict(self)}{', ' + subs if subs else ''})" def __eq__(self, other): if len(self.subcomponents) != len(other.subcomponents): return False properties_equal = super().__eq__(other) if not properties_equal: return False # The subcomponents might not be in the same order, # neither there's a natural key we can sort the subcomponents by nor # are the subcomponent types hashable, so we cant put them in a set to # check for set equivalence. We have to iterate over the subcomponents # and look for each of them in the list. for subcomponent in self.subcomponents: if subcomponent not in other.subcomponents: return False return True DTSTAMP = single_utc_property( "DTSTAMP", """RFC 5545: Conformance: This property MUST be included in the "VEVENT", "VTODO", "VJOURNAL", or "VFREEBUSY" calendar components. Description: In the case of an iCalendar object that specifies a "METHOD" property, this property specifies the date and time that the instance of the iCalendar object was created. In the case of an iCalendar object that doesn't specify a "METHOD" property, this property specifies the date and time that the information associated with the calendar component was last revised in the calendar store. The value MUST be specified in the UTC time format. In the case of an iCalendar object that doesn't specify a "METHOD" property, this property is equivalent to the "LAST-MODIFIED" property. """, ) LAST_MODIFIED = single_utc_property( "LAST-MODIFIED", """RFC 5545: Purpose: This property specifies the date and time that the information associated with the calendar component was last revised in the calendar store. Note: This is analogous to the modification date and time for a file in the file system. Conformance: This property can be specified in the "VEVENT", "VTODO", "VJOURNAL", or "VTIMEZONE" calendar components. """, ) def is_thunderbird(self) -> bool: """Whether this component has attributes that indicate that Mozilla Thunderbird created it.""" return any(attr.startswith("X-MOZ-") for attr in self.keys()) ####################################### # components defined in RFC 5545 def create_single_property( prop: str, value_attr: Optional[str], value_type: tuple[type], type_def: type, doc: str, vProp: type = vDDDTypes, # noqa: N803 ): """Create a single property getter and setter. :param prop: The name of the property. :param value_attr: The name of the attribute to get the value from. :param value_type: The type of the value. :param type_def: The type of the property. :param doc: The docstring of the property. :param vProp: The type of the property from :mod:`icalendar.prop`. """ def p_get(self: Component): default = object() result = self.get(prop, default) if result is default: return None if isinstance(result, list): raise InvalidCalendar(f"Multiple {prop} defined.") value = result if value_attr is None else getattr(result, value_attr, result) if not isinstance(value, value_type): raise InvalidCalendar( f"{prop} must be either a {' or '.join(t.__name__ for t in value_type)}, not {value}." ) return value def p_set(self: Component, value) -> None: if value is None: p_del(self) return if not isinstance(value, value_type): raise TypeError( f"Use {' or '.join(t.__name__ for t in value_type)}, not {type(value).__name__}." ) self[prop] = vProp(value) if prop in self.exclusive: for other_prop in self.exclusive: if other_prop != prop: self.pop(other_prop, None) p_set.__annotations__["value"] = p_get.__annotations__["return"] = Optional[ type_def ] def p_del(self: Component): self.pop(prop) p_doc = f"""The {prop} property. {doc} Accepted values: {', '.join(t.__name__ for t in value_type)}. If the attribute has invalid values, we raise InvalidCalendar. If the value is absent, we return None. You can also delete the value with del or by setting it to None. """ return property(p_get, p_set, p_del, p_doc) _X_MOZ_SNOOZE_TIME = single_utc_property( "X-MOZ-SNOOZE-TIME", "Thunderbird: Alarms before this time are snoozed." ) _X_MOZ_LASTACK = single_utc_property( "X-MOZ-LASTACK", "Thunderbird: Alarms before this time are acknowledged." ) def _get_duration(self: Component) -> Optional[timedelta]: """Getter for property DURATION.""" default = object() duration = self.get("duration", default) if isinstance(duration, vDDDTypes): return duration.dt if isinstance(duration, vDuration): return duration.td if duration is not default and not isinstance(duration, timedelta): raise InvalidCalendar( f"DURATION must be a timedelta, not {type(duration).__name__}." ) return None def _set_duration(self: Component, value: Optional[timedelta]): """Setter for property DURATION.""" if value is None: self.pop("duration", None) return if not isinstance(value, timedelta): raise TypeError(f"Use timedelta, not {type(value).__name__}.") self["duration"] = vDuration(value) self.pop("DTEND") self.pop("DUE") def _del_duration(self: Component): """Delete property DURATION.""" self.pop("DURATION") _doc_duration = """The DURATION property. The "DTSTART" property for a "{component}" specifies the inclusive start of the event. The "DURATION" property in conjunction with the DTSTART property for a "{component}" calendar component specifies the non-inclusive end of the event. If you would like to calculate the duration of a {component}, do not use this. Instead use the duration property (lower case). """ class Event(Component): """ A "VEVENT" calendar component is a grouping of component properties that represents a scheduled amount of time on a calendar. For example, it can be an activity, such as a one-hour long department meeting from 8:00 AM to 9:00 AM, tomorrow. """ name = "VEVENT" canonical_order = ( "SUMMARY", "DTSTART", "DTEND", "DURATION", "DTSTAMP", "UID", "RECURRENCE-ID", "SEQUENCE", "RRULE", "RDATE", "EXDATE", ) required = ( "UID", "DTSTAMP", ) singletons = ( "CLASS", "CREATED", "COLOR", "DESCRIPTION", "DTSTART", "GEO", "LAST-MODIFIED", "LOCATION", "ORGANIZER", "PRIORITY", "DTSTAMP", "SEQUENCE", "STATUS", "SUMMARY", "TRANSP", "URL", "RECURRENCE-ID", "DTEND", "DURATION", "UID", ) exclusive = ( "DTEND", "DURATION", ) multiple = ( "ATTACH", "ATTENDEE", "CATEGORIES", "COMMENT", "CONTACT", "EXDATE", "RSTATUS", "RELATED", "RESOURCES", "RDATE", "RRULE", ) ignore_exceptions = True @property def alarms(self) -> Alarms: """Compute the alarm times for this component. >>> from icalendar import Event >>> event = Event.example("rfc_9074_example_1") >>> len(event.alarms.times) 1 >>> alarm_time = event.alarms.times[0] >>> alarm_time.trigger # The time when the alarm pops up datetime.datetime(2021, 3, 2, 10, 15, tzinfo=ZoneInfo(key='America/New_York')) >>> alarm_time.is_active() # This alarm has not been acknowledged True Note that this only uses DTSTART and DTEND, but ignores RDATE, EXDATE, and RRULE properties. """ from icalendar.alarms import Alarms return Alarms(self) @classmethod def example(cls, name: str = "rfc_9074_example_3") -> Event: """Return the calendar example with the given name.""" return cls.from_ical(get_example("events", name)) DTSTART = create_single_property( "DTSTART", "dt", (datetime, date), date, 'The "DTSTART" property for a "VEVENT" specifies the inclusive start of the event.', ) DTEND = create_single_property( "DTEND", "dt", (datetime, date), date, 'The "DTEND" property for a "VEVENT" calendar component specifies the non-inclusive end of the event.', ) def _get_start_end_duration(self): """Verify the calendar validity and return the right attributes.""" start = self.DTSTART end = self.DTEND duration = self.DURATION if duration is not None and end is not None: raise InvalidCalendar( "Only one of DTEND and DURATION may be in a VEVENT, not both." ) if ( isinstance(start, date) and not isinstance(start, datetime) and duration is not None and duration.seconds != 0 ): raise InvalidCalendar( "When DTSTART is a date, DURATION must be of days or weeks." ) if start is not None and end is not None and is_date(start) != is_date(end): raise InvalidCalendar( "DTSTART and DTEND must be of the same type, either date or datetime." ) return start, end, duration DURATION = property( _get_duration, _set_duration, _del_duration, _doc_duration.format(component="VEVENT"), ) @property def duration(self) -> timedelta: """The duration of the VEVENT. This duration is calculated from the start and end of the event. You cannot set the duration as it is unclear what happens to start and end. """ return self.end - self.start @property def start(self) -> date | datetime: """The start of the component. Invalid values raise an InvalidCalendar. If there is no start, we also raise an IncompleteComponent error. You can get the start, end and duration of an event as follows: >>> from datetime import datetime >>> from icalendar import Event >>> event = Event() >>> event.start = datetime(2021, 1, 1, 12) >>> event.end = datetime(2021, 1, 1, 12, 30) # 30 minutes >>> event.duration # 1800 seconds == 30 minutes datetime.timedelta(seconds=1800) >>> print(event.to_ical()) BEGIN:VEVENT DTSTART:20210101T120000 DTEND:20210101T123000 END:VEVENT """ start = self._get_start_end_duration()[0] if start is None: raise IncompleteComponent("No DTSTART given.") return start @start.setter def start(self, start: Optional[date | datetime]): """Set the start.""" self.DTSTART = start @property def end(self) -> date | datetime: """The end of the component. Invalid values raise an InvalidCalendar error. If there is no end, we also raise an IncompleteComponent error. """ start, end, duration = self._get_start_end_duration() if end is None and duration is None: if start is None: raise IncompleteComponent("No DTEND or DURATION+DTSTART given.") if is_date(start): return start + timedelta(days=1) return start if duration is not None: if start is not None: return start + duration raise IncompleteComponent("No DTEND or DURATION+DTSTART given.") return end @end.setter def end(self, end: date | datetime | None): """Set the end.""" self.DTEND = end X_MOZ_SNOOZE_TIME = _X_MOZ_SNOOZE_TIME X_MOZ_LASTACK = _X_MOZ_LASTACK color = color_property sequence = sequence_property categories = categories_property rdates = rdates_property exdates = exdates_property rrules = rrules_property uid = uid_property class Todo(Component): """ A "VTODO" calendar component is a grouping of component properties that represents an action item or assignment. For example, it can be used to represent an item of work assigned to an individual, such as "Prepare for the upcoming conference seminar on Internet Calendaring". """ name = "VTODO" required = ( "UID", "DTSTAMP", ) singletons = ( "CLASS", "COLOR", "COMPLETED", "CREATED", "DESCRIPTION", "DTSTAMP", "DTSTART", "GEO", "LAST-MODIFIED", "LOCATION", "ORGANIZER", "PERCENT-COMPLETE", "PRIORITY", "RECURRENCE-ID", "SEQUENCE", "STATUS", "SUMMARY", "UID", "URL", "DUE", "DURATION", ) exclusive = ( "DUE", "DURATION", ) multiple = ( "ATTACH", "ATTENDEE", "CATEGORIES", "COMMENT", "CONTACT", "EXDATE", "RSTATUS", "RELATED", "RESOURCES", "RDATE", "RRULE", ) DTSTART = create_single_property( "DTSTART", "dt", (datetime, date), date, 'The "DTSTART" property for a "VTODO" specifies the inclusive start of the Todo.', ) DUE = create_single_property( "DUE", "dt", (datetime, date), date, 'The "DUE" property for a "VTODO" calendar component specifies the non-inclusive end of the Todo.', ) DURATION = property( _get_duration, _set_duration, _del_duration, _doc_duration.format(component="VTODO"), ) def _get_start_end_duration(self): """Verify the calendar validity and return the right attributes.""" start = self.DTSTART end = self.DUE duration = self.DURATION if duration is not None and end is not None: raise InvalidCalendar( "Only one of DUE and DURATION may be in a VTODO, not both." ) if ( isinstance(start, date) and not isinstance(start, datetime) and duration is not None and duration.seconds != 0 ): raise InvalidCalendar( "When DTSTART is a date, DURATION must be of days or weeks." ) if start is not None and end is not None and is_date(start) != is_date(end): raise InvalidCalendar( "DTSTART and DUE must be of the same type, either date or datetime." ) return start, end, duration @property def start(self) -> date | datetime: """The start of the VTODO. Invalid values raise an InvalidCalendar. If there is no start, we also raise an IncompleteComponent error. You can get the start, end and duration of a Todo as follows: >>> from datetime import datetime >>> from icalendar import Todo >>> todo = Todo() >>> todo.start = datetime(2021, 1, 1, 12) >>> todo.end = datetime(2021, 1, 1, 12, 30) # 30 minutes >>> todo.duration # 1800 seconds == 30 minutes datetime.timedelta(seconds=1800) >>> print(todo.to_ical()) BEGIN:VTODO DTSTART:20210101T120000 DUE:20210101T123000 END:VTODO """ start = self._get_start_end_duration()[0] if start is None: raise IncompleteComponent("No DTSTART given.") return start @start.setter def start(self, start: Optional[date | datetime]): """Set the start.""" self.DTSTART = start @property def end(self) -> date | datetime: """The end of the component. Invalid values raise an InvalidCalendar error. If there is no end, we also raise an IncompleteComponent error. """ start, end, duration = self._get_start_end_duration() if end is None and duration is None: if start is None: raise IncompleteComponent("No DUE or DURATION+DTSTART given.") if is_date(start): return start + timedelta(days=1) return start if duration is not None: if start is not None: return start + duration raise IncompleteComponent("No DUE or DURATION+DTSTART given.") return end @end.setter def end(self, end: date | datetime | None): """Set the end.""" self.DUE = end @property def duration(self) -> timedelta: """The duration of the VTODO. This duration is calculated from the start and end of the Todo. You cannot set the duration as it is unclear what happens to start and end. """ return self.end - self.start X_MOZ_SNOOZE_TIME = _X_MOZ_SNOOZE_TIME X_MOZ_LASTACK = _X_MOZ_LASTACK @property def alarms(self) -> Alarms: """Compute the alarm times for this component. >>> from datetime import datetime >>> from icalendar import Todo >>> todo = Todo() # empty without alarms >>> todo.start = datetime(2024, 10, 26, 10, 21) >>> len(todo.alarms.times) 0 Note that this only uses DTSTART and DUE, but ignores RDATE, EXDATE, and RRULE properties. """ from icalendar.alarms import Alarms return Alarms(self) color = color_property sequence = sequence_property categories = categories_property rdates = rdates_property exdates = exdates_property rrules = rrules_property uid = uid_property class Journal(Component): """A descriptive text at a certain time or associated with a component. A "VJOURNAL" calendar component is a grouping of component properties that represent one or more descriptive text notes associated with a particular calendar date. The "DTSTART" property is used to specify the calendar date with which the journal entry is associated. Generally, it will have a DATE value data type, but it can also be used to specify a DATE-TIME value data type. Examples of a journal entry include a daily record of a legislative body or a journal entry of individual telephone contacts for the day or an ordered list of accomplishments for the day. """ name = "VJOURNAL" required = ( "UID", "DTSTAMP", ) singletons = ( "CLASS", "COLOR", "CREATED", "DTSTART", "DTSTAMP", "LAST-MODIFIED", "ORGANIZER", "RECURRENCE-ID", "SEQUENCE", "STATUS", "SUMMARY", "UID", "URL", ) multiple = ( "ATTACH", "ATTENDEE", "CATEGORIES", "COMMENT", "CONTACT", "EXDATE", "RELATED", "RDATE", "RRULE", "RSTATUS", "DESCRIPTION", ) DTSTART = create_single_property( "DTSTART", "dt", (datetime, date), date, 'The "DTSTART" property for a "VJOURNAL" that specifies the exact date at which the journal entry was made.', ) @property def start(self) -> date: """The start of the Journal. The "DTSTART" property is used to specify the calendar date with which the journal entry is associated. """ start = self.DTSTART if start is None: raise IncompleteComponent("No DTSTART given.") return start @start.setter def start(self, value: datetime | date) -> None: """Set the start of the journal.""" self.DTSTART = value end = start @property def duration(self) -> timedelta: """The journal has no duration: timedelta(0).""" return timedelta(0) color = color_property sequence = sequence_property categories = categories_property rdates = rdates_property exdates = exdates_property rrules = rrules_property uid = uid_property class FreeBusy(Component): """ A "VFREEBUSY" calendar component is a grouping of component properties that represents either a request for free or busy time information, a reply to a request for free or busy time information, or a published set of busy time information. """ name = "VFREEBUSY" required = ( "UID", "DTSTAMP", ) singletons = ( "CONTACT", "DTSTART", "DTEND", "DTSTAMP", "ORGANIZER", "UID", "URL", ) multiple = ( "ATTENDEE", "COMMENT", "FREEBUSY", "RSTATUS", ) uid = uid_property class Timezone(Component): """ A "VTIMEZONE" calendar component is a grouping of component properties that defines a time zone. It is used to describe the way in which a time zone changes its offset from UTC over time. """ subcomponents: list[TimezoneStandard|TimezoneDaylight] name = "VTIMEZONE" canonical_order = ("TZID",) required = ("TZID",) # it also requires one of components DAYLIGHT and STANDARD singletons = ( "TZID", "LAST-MODIFIED", "TZURL", ) _DEFAULT_FIRST_DATE = date(1970, 1, 1) _DEFAULT_LAST_DATE = date(2038, 1, 1) @classmethod def example(cls, name: str = "pacific_fiji") -> Calendar: """Return the timezone example with the given name.""" return cls.from_ical(get_example("timezones", name)) @staticmethod def _extract_offsets(component: TimezoneDaylight | TimezoneStandard, tzname: str): """extract offsets and transition times from a VTIMEZONE component :param component: a STANDARD or DAYLIGHT component :param tzname: the name of the zone """ offsetfrom = component.TZOFFSETFROM offsetto = component.TZOFFSETTO dtstart = component.DTSTART # offsets need to be rounded to the next minute, we might loose up # to 30 seconds accuracy, but it can't be helped (datetime # supposedly cannot handle smaller offsets) offsetto_s = int((offsetto.seconds + 30) / 60) * 60 offsetto = timedelta(days=offsetto.days, seconds=offsetto_s) offsetfrom_s = int((offsetfrom.seconds + 30) / 60) * 60 offsetfrom = timedelta(days=offsetfrom.days, seconds=offsetfrom_s) # expand recurrences if "RRULE" in component: # to be paranoid about correct weekdays # evaluate the rrule with the current offset tzi = dateutil.tz.tzoffset("(offsetfrom)", offsetfrom) rrstart = dtstart.replace(tzinfo=tzi) rrulestr = component["RRULE"].to_ical().decode("utf-8") rrule = dateutil.rrule.rrulestr(rrulestr, dtstart=rrstart) tzp.fix_rrule_until(rrule, component["RRULE"]) # constructing the timezone requires UTC transition times. # here we construct local times without tzinfo, the offset to UTC # gets subtracted in to_tz(). transtimes = [dt.replace(tzinfo=None) for dt in rrule] # or rdates elif "RDATE" in component: if not isinstance(component["RDATE"], list): rdates = [component["RDATE"]] else: rdates = component["RDATE"] transtimes = [dtstart] + [leaf.dt for tree in rdates for leaf in tree.dts] else: transtimes = [dtstart] transitions = [ (transtime, offsetfrom, offsetto, tzname) for transtime in set(transtimes) ] if component.name == "STANDARD": is_dst = 0 elif component.name == "DAYLIGHT": is_dst = 1 return is_dst, transitions @staticmethod def _make_unique_tzname(tzname, tznames): """ :param tzname: Candidate tzname :param tznames: Other tznames """ # TODO better way of making sure tznames are unique while tzname in tznames: tzname += "_1" tznames.add(tzname) return tzname def to_tz(self, tzp: TZP = tzp, lookup_tzid: bool = True): """convert this VTIMEZONE component to a timezone object :param tzp: timezone provider to use :param lookup_tzid: whether to use the TZID property to look up existing timezone definitions with tzp. If it is False, a new timezone will be created. If it is True, the existing timezone will be used if it exists, otherwise a new timezone will be created. """ if lookup_tzid: tz = tzp.timezone(self.tz_name) if tz is not None: return tz return tzp.create_timezone(self) @property def tz_name(self) -> str: """Return the name of the timezone component. Please note that the names of the timezone are different from this name and may change with winter/summer time. """ try: return str(self["TZID"]) except UnicodeEncodeError: return self["TZID"].encode("ascii", "replace") def get_transitions( self, ) -> Tuple[List[datetime], List[Tuple[timedelta, timedelta, str]]]: """Return a tuple of (transition_times, transition_info) - transition_times = [datetime, ...] - transition_info = [(TZOFFSETTO, dts_offset, tzname)] """ zone = self.tz_name transitions = [] dst = {} tznames = set() for component in self.walk(): if type(component) == Timezone: continue if is_date(component["DTSTART"].dt): component.DTSTART = to_datetime(component["DTSTART"].dt) assert isinstance( component["DTSTART"].dt, datetime ), "VTIMEZONEs sub-components' DTSTART must be of type datetime, not date" try: tzname = str(component["TZNAME"]) except UnicodeEncodeError: tzname = component["TZNAME"].encode("ascii", "replace") tzname = self._make_unique_tzname(tzname, tznames) except KeyError: # for whatever reason this is str/unicode tzname = ( f"{zone}_{component['DTSTART'].to_ical().decode('utf-8')}_" + f"{component['TZOFFSETFROM'].to_ical()}_" + f"{component['TZOFFSETTO'].to_ical()}" ) tzname = self._make_unique_tzname(tzname, tznames) dst[tzname], component_transitions = self._extract_offsets( component, tzname ) transitions.extend(component_transitions) transitions.sort() transition_times = [ transtime - osfrom for transtime, osfrom, _, _ in transitions ] # transition_info is a list with tuples in the format # (utcoffset, dstoffset, name) # dstoffset = 0, if current transition is to standard time # = this_utcoffset - prev_standard_utcoffset, otherwise transition_info = [] for num, (transtime, osfrom, osto, name) in enumerate(transitions): dst_offset = False if not dst[name]: dst_offset = timedelta(seconds=0) else: # go back in time until we find a transition to dst for index in range(num - 1, -1, -1): if not dst[transitions[index][3]]: # [3] is the name dst_offset = osto - transitions[index][2] # [2] is osto # noqa break # when the first transition is to dst, we didn't find anything # in the past, so we have to look into the future if not dst_offset: for index in range(num, len(transitions)): if not dst[transitions[index][3]]: # [3] is the name dst_offset = ( osto - transitions[index][2] ) # [2] is osto # noqa break assert dst_offset is not False transition_info.append((osto, dst_offset, name)) return transition_times, transition_info # binary search _from_tzinfo_skip_search = [ timedelta(days=days) for days in (64, 32, 16, 8, 4, 2, 1) ] + [ # we know it happens in the night usually around 1am timedelta(hours=4), timedelta(hours=1), # adding some minutes and seconds for faster search timedelta(minutes=20), timedelta(minutes=5), timedelta(minutes=1), timedelta(seconds=20), timedelta(seconds=5), timedelta(seconds=1), ] @classmethod def from_tzinfo( cls, timezone: tzinfo, tzid: Optional[str] = None, first_date: date = _DEFAULT_FIRST_DATE, last_date: date = _DEFAULT_LAST_DATE, ) -> Timezone: """Return a VTIMEZONE component from a timezone object. This works with pytz and zoneinfo and any other timezone. The offsets are calculated from the tzinfo object. Parameters: :param tzinfo: the timezone object :param tzid: the tzid for this timezone. If None, it will be extracted from the tzinfo. :param first_date: a datetime that is earlier than anything that happens in the calendar :param last_date: a datetime that is later than anything that happens in the calendar :raises ValueError: If we have no tzid and cannot extract one. .. note:: This can take some time. Please cache the results. """ if tzid is None: tzid = tzid_from_tzinfo(timezone) if tzid is None: raise ValueError( f"Cannot get TZID from {timezone}. Please set the tzid parameter." ) normalize = getattr(timezone, "normalize", lambda dt: dt) # pytz compatibility first_datetime = datetime(first_date.year, first_date.month, first_date.day) # noqa: DTZ001 last_datetime = datetime(last_date.year, last_date.month, last_date.day) # noqa: DTZ001 if hasattr(timezone, "localize"): # pytz compatibility first_datetime = timezone.localize(first_datetime) last_datetime = timezone.localize(last_datetime) else: first_datetime = first_datetime.replace(tzinfo=timezone) last_datetime = last_datetime.replace(tzinfo=timezone) # from, to, tzname, is_standard -> start offsets: dict[ tuple[Optional[timedelta], timedelta, str, bool], list[datetime] ] = defaultdict(list) start = first_datetime offset_to = None while start < last_datetime: offset_from = offset_to end = start offset_to = end.utcoffset() for add_offset in cls._from_tzinfo_skip_search: last_end = end # we need to save this as we might be left and right of the time change end = normalize(end + add_offset) try: while end.utcoffset() == offset_to: last_end = end end = normalize(end + add_offset) except OverflowError: # zoninfo does not go all the way break # retract if we overshoot end = last_end # Now, start (inclusive) -> end (exclusive) are one timezone is_standard = start.dst() == timedelta() name = start.tzname() if name is None: name = str(offset_to) key = (offset_from, offset_to, name, is_standard) # first_key = (None,) + key[1:] # if first_key in offsets: # # remove the first one and claim it changes at that day # offsets[first_key] = offsets.pop(first_key) offsets[key].append(start.replace(tzinfo=None)) start = normalize(end + cls._from_tzinfo_skip_search[-1]) tz = cls() tz.add("TZID", tzid) tz.add("COMMENT", f"This timezone only works from {first_date} to {last_date}.") for (offset_from, offset_to, tzname, is_standard), starts in offsets.items(): first_start = min(starts) starts.remove(first_start) if first_start.date() == last_date: first_start = datetime(last_date.year, last_date.month, last_date.day) # noqa: DTZ001 subcomponent = TimezoneStandard() if is_standard else TimezoneDaylight() if offset_from is None: offset_from = offset_to # noqa: PLW2901 subcomponent.TZOFFSETFROM = offset_from subcomponent.TZOFFSETTO = offset_to subcomponent.add("TZNAME", tzname) subcomponent.DTSTART = first_start if starts: subcomponent.add("RDATE", starts) tz.add_component(subcomponent) return tz @classmethod def from_tzid( cls, tzid: str, tzp: TZP = tzp, first_date: date = _DEFAULT_FIRST_DATE, last_date: date = _DEFAULT_LAST_DATE, ) -> Timezone: """Create a VTIMEZONE from a tzid like ``"Europe/Berlin"``. :param tzid: the id of the timezone :param tzp: the timezone provider :param first_date: a datetime that is earlier than anything that happens in the calendar :param last_date: a datetime that is later than anything that happens in the calendar :raises ValueError: If the tzid is unknown. >>> from icalendar import Timezone >>> tz = Timezone.from_tzid("Europe/Berlin") >>> print(tz.to_ical()[:36]) BEGIN:VTIMEZONE TZID:Europe/Berlin .. note:: This can take some time. Please cache the results. """ tz = tzp.timezone(tzid) if tz is None: raise ValueError(f"Unkown timezone {tzid}.") return cls.from_tzinfo(tz, tzid, first_date, last_date) @property def standard(self) -> list[TimezoneStandard]: """The STANDARD subcomponents as a list.""" return self.walk("STANDARD") @property def daylight(self) -> list[TimezoneDaylight]: """The DAYLIGHT subcomponents as a list. These are for the daylight saving time. """ return self.walk("DAYLIGHT") class TimezoneStandard(Component): """ The "STANDARD" sub-component of "VTIMEZONE" defines the standard time offset from UTC for a time zone. It represents a time zone's standard time, typically used during winter months in locations that observe Daylight Saving Time. """ name = "STANDARD" required = ("DTSTART", "TZOFFSETTO", "TZOFFSETFROM") singletons = ( "DTSTART", "TZOFFSETTO", "TZOFFSETFROM", ) multiple = ("COMMENT", "RDATE", "TZNAME", "RRULE", "EXDATE") DTSTART = create_single_property( "DTSTART", "dt", (datetime,), datetime, """The mandatory "DTSTART" property gives the effective onset date and local time for the time zone sub-component definition. "DTSTART" in this usage MUST be specified as a date with a local time value.""", ) TZOFFSETTO = create_single_property( "TZOFFSETTO", "td", (timedelta,), timedelta, """The mandatory "TZOFFSETTO" property gives the UTC offset for the time zone sub-component (Standard Time or Daylight Saving Time) when this observance is in use. """, vUTCOffset, ) TZOFFSETFROM = create_single_property( "TZOFFSETFROM", "td", (timedelta,), timedelta, """The mandatory "TZOFFSETFROM" property gives the UTC offset that is in use when the onset of this time zone observance begins. "TZOFFSETFROM" is combined with "DTSTART" to define the effective onset for the time zone sub-component definition. For example, the following represents the time at which the observance of Standard Time took effect in Fall 1967 for New York City: DTSTART:19671029T020000 TZOFFSETFROM:-0400 """, vUTCOffset, ) rdates = rdates_property exdates = exdates_property rrules = rrules_property class TimezoneDaylight(Component): """ The "DAYLIGHT" sub-component of "VTIMEZONE" defines the daylight saving time offset from UTC for a time zone. It represents a time zone's daylight saving time, typically used during summer months in locations that observe Daylight Saving Time. """ name = "DAYLIGHT" required = TimezoneStandard.required singletons = TimezoneStandard.singletons multiple = TimezoneStandard.multiple DTSTART = TimezoneStandard.DTSTART TZOFFSETTO = TimezoneStandard.TZOFFSETTO TZOFFSETFROM = TimezoneStandard.TZOFFSETFROM rdates = rdates_property exdates = exdates_property rrules = rrules_property class Alarm(Component): """ A "VALARM" calendar component is a grouping of component properties that defines an alarm or reminder for an event or a to-do. For example, it may be used to define a reminder for a pending event or an overdue to-do. """ name = "VALARM" # some properties MAY/MUST/MUST NOT appear depending on ACTION value required = ( "ACTION", "TRIGGER", ) singletons = ( "ATTACH", "ACTION", "DESCRIPTION", "SUMMARY", "TRIGGER", "DURATION", "REPEAT", "UID", "PROXIMITY", "ACKNOWLEDGED", ) inclusive = ( ( "DURATION", "REPEAT", ), ( "SUMMARY", "ATTENDEE", ), ) multiple = ("ATTENDEE", "ATTACH", "RELATED-TO") REPEAT = single_int_property( "REPEAT", 0, """The REPEAT property of an alarm component. The alarm can be defined such that it triggers repeatedly. A definition of an alarm with a repeating trigger MUST include both the "DURATION" and "REPEAT" properties. The "DURATION" property specifies the delay period, after which the alarm will repeat. The "REPEAT" property specifies the number of additional repetitions that the alarm will be triggered. This repetition count is in addition to the initial triggering of the alarm. """ ) DURATION = property( _get_duration, _set_duration, _del_duration, """The DURATION property of an alarm component. The alarm can be defined such that it triggers repeatedly. A definition of an alarm with a repeating trigger MUST include both the "DURATION" and "REPEAT" properties. The "DURATION" property specifies the delay period, after which the alarm will repeat. """, ) ACKNOWLEDGED = single_utc_property( "ACKNOWLEDGED", """This is defined in RFC 9074: Purpose: This property specifies the UTC date and time at which the corresponding alarm was last sent or acknowledged. This property is used to specify when an alarm was last sent or acknowledged. This allows clients to determine when a pending alarm has been acknowledged by a calendar user so that any alerts can be dismissed across multiple devices. It also allows clients to track repeating alarms or alarms on recurring events or to-dos to ensure that the right number of missed alarms can be tracked. Clients SHOULD set this property to the current date-time value in UTC when a calendar user acknowledges a pending alarm. Certain kinds of alarms, such as email-based alerts, might not provide feedback as to when the calendar user sees them. For those kinds of alarms, the client SHOULD set this property when the alarm is triggered and the action is successfully carried out. When an alarm is triggered on a client, clients can check to see if an "ACKNOWLEDGED" property is present. If it is, and the value of that property is greater than or equal to the computed trigger time for the alarm, then the client SHOULD NOT trigger the alarm. Similarly, if an alarm has been triggered and an "alert" has been presented to a calendar user, clients can monitor the iCalendar data to determine whether an "ACKNOWLEDGED" property is added or changed in the alarm component. If the value of any "ACKNOWLEDGED" property in the alarm changes and is greater than or equal to the trigger time of the alarm, then clients SHOULD dismiss or cancel any "alert" presented to the calendar user. """, ) TRIGGER = create_single_property( "TRIGGER", "dt", (datetime, timedelta), Optional[Union[timedelta, datetime]], """Purpose: This property specifies when an alarm will trigger. Value Type: The default value type is DURATION. The value type can be set to a DATE-TIME value type, in which case the value MUST specify a UTC-formatted DATE-TIME value. Either a positive or negative duration may be specified for the "TRIGGER" property. An alarm with a positive duration is triggered after the associated start or end of the event or to-do. An alarm with a negative duration is triggered before the associated start or end of the event or to-do.""", ) @property def TRIGGER_RELATED(self) -> str: """The RELATED parameter of the TRIGGER property. Values are either "START" (default) or "END". A value of START will set the alarm to trigger off the start of the associated event or to-do. A value of END will set the alarm to trigger off the end of the associated event or to-do. In this example, we create an alarm that triggers two hours after the end of its parent component: >>> from icalendar import Alarm >>> from datetime import timedelta >>> alarm = Alarm() >>> alarm.TRIGGER = timedelta(hours=2) >>> alarm.TRIGGER_RELATED = "END" """ trigger = self.get("TRIGGER") if trigger is None: return "START" return trigger.params.get("RELATED", "START") @TRIGGER_RELATED.setter def TRIGGER_RELATED(self, value: str): """Set "START" or "END".""" trigger = self.get("TRIGGER") if trigger is None: raise ValueError( "You must set a TRIGGER before setting the RELATED parameter." ) trigger.params["RELATED"] = value class Triggers(NamedTuple): """The computed times of alarm triggers. start - triggers relative to the start of the Event or Todo (timedelta) end - triggers relative to the end of the Event or Todo (timedelta) absolute - triggers at a datetime in UTC """ start: tuple[timedelta] end: tuple[timedelta] absolute: tuple[datetime] @property def triggers(self): """The computed triggers of an Alarm. This takes the TRIGGER, DURATION and REPEAT properties into account. Here, we create an alarm that triggers 3 times before the start of the parent component: >>> from icalendar import Alarm >>> from datetime import timedelta >>> alarm = Alarm() >>> alarm.TRIGGER = timedelta(hours=-4) # trigger 4 hours before START >>> alarm.DURATION = timedelta(hours=1) # after 1 hour trigger again >>> alarm.REPEAT = 2 # trigger 2 more times >>> alarm.triggers.start == (timedelta(hours=-4), timedelta(hours=-3), timedelta(hours=-2)) True >>> alarm.triggers.end () >>> alarm.triggers.absolute () """ start = [] end = [] absolute = [] trigger = self.TRIGGER if trigger is not None: if isinstance(trigger, date): absolute.append(trigger) add = absolute elif self.TRIGGER_RELATED == "START": start.append(trigger) add = start else: end.append(trigger) add = end duration = self.DURATION if duration is not None: for _ in range(self.REPEAT): add.append(add[-1] + duration) return self.Triggers( start=tuple(start), end=tuple(end), absolute=tuple(absolute) ) uid = single_string_property( "UID", uid_property.__doc__, "X-ALARMUID", ) class Calendar(Component): """ The "VCALENDAR" object is a collection of calendar information. This information can include a variety of components, such as "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VTIMEZONE", or any other type of calendar component. """ name = "VCALENDAR" canonical_order = ( "VERSION", "PRODID", "CALSCALE", "METHOD", "DESCRIPTION", "X-WR-CALDESC", "NAME", "X-WR-CALNAME", ) required = ( "PRODID", "VERSION", ) singletons = ( "PRODID", "VERSION", "CALSCALE", "METHOD", "COLOR", # RFC 7986 ) multiple = ( "CATEGORIES", # RFC 7986 "DESCRIPTION", # RFC 7986 "NAME", # RFC 7986 ) @classmethod def example(cls, name: str = "example") -> Calendar: """Return the calendar example with the given name.""" return cls.from_ical(get_example("calendars", name)) @classmethod def from_ical(cls, st, multiple=False): comps = Component.from_ical(st, multiple=True) all_timezones_so_far = True for comp in comps: for component in comp.subcomponents: if component.name == 'VTIMEZONE': if all_timezones_so_far: pass else: # If a preceding component refers to a VTIMEZONE defined later in the source st # (forward references are allowed by RFC 5545), then the earlier component may have # the wrong timezone attached. # However, during computation of comps, all VTIMEZONEs observed do end up in # the timezone cache. So simply re-running from_ical will rely on the cache # for those forward references to produce the correct result. # See test_create_america_new_york_forward_reference. return Component.from_ical(st, multiple) else: all_timezones_so_far = False # No potentially forward VTIMEZONEs to worry about if multiple: return comps if len(comps) > 1: raise ValueError(cls._format_error( 'Found multiple components where only one is allowed', st)) if len(comps) < 1: raise ValueError(cls._format_error( 'Found no components where exactly one is required', st)) return comps[0] @property def events(self) -> list[Event]: """All event components in the calendar. This is a shortcut to get all events. Modifications do not change the calendar. Use :py:meth:`Component.add_component`. >>> from icalendar import Calendar >>> calendar = Calendar.example() >>> event = calendar.events[0] >>> event.start datetime.date(2022, 1, 1) >>> print(event["SUMMARY"]) New Year's Day """ return self.walk("VEVENT") @property def todos(self) -> list[Todo]: """All todo components in the calendar. This is a shortcut to get all todos. Modifications do not change the calendar. Use :py:meth:`Component.add_component`. """ return self.walk("VTODO") @property def freebusy(self) -> list[FreeBusy]: """All FreeBusy components in the calendar. This is a shortcut to get all FreeBusy. Modifications do not change the calendar. Use :py:meth:`Component.add_component`. """ return self.walk("VFREEBUSY") def get_used_tzids(self) -> set[str]: """The set of TZIDs in use. This goes through the whole calendar to find all occurrences of timezone information like the TZID parameter in all attributes. >>> from icalendar import Calendar >>> calendar = Calendar.example("timezone_rdate") >>> calendar.get_used_tzids() {'posix/Europe/Vaduz'} Even if you use UTC, this will not show up. """ result = set() for name, value in self.property_items(sorted=False): if hasattr(value, "params"): result.add(value.params.get("TZID")) return result - {None} def get_missing_tzids(self) -> set[str]: """The set of missing timezone component tzids. To create a :rfc:`5545` compatible calendar, all of these timezones should be added. """ tzids = self.get_used_tzids() for timezone in self.timezones: tzids.remove(timezone.tz_name) return tzids @property def timezones(self) -> list[Timezone]: """Return the timezones components in this calendar. >>> from icalendar import Calendar >>> calendar = Calendar.example("pacific_fiji") >>> [timezone.tz_name for timezone in calendar.timezones] ['custom_Pacific/Fiji'] .. note:: This is a read-only property. """ return self.walk("VTIMEZONE") def add_missing_timezones( self, first_date: date = Timezone._DEFAULT_FIRST_DATE, last_date: date = Timezone._DEFAULT_LAST_DATE, ): """Add all missing VTIMEZONE components. This adds all the timezone components that are required. .. note:: Timezones that are not known will not be added. :param first_date: earlier than anything that happens in the calendar :param last_date: later than anything happening in the calendar >>> from icalendar import Calendar, Event >>> from datetime import datetime >>> from zoneinfo import ZoneInfo >>> calendar = Calendar() >>> event = Event() >>> calendar.add_component(event) >>> event.start = datetime(1990, 10, 11, 12, tzinfo=ZoneInfo("Europe/Berlin")) >>> calendar.timezones [] >>> calendar.add_missing_timezones() >>> calendar.timezones[0].tz_name 'Europe/Berlin' >>> calendar.get_missing_tzids() # check that all are added set() """ for tzid in self.get_missing_tzids(): try: timezone = Timezone.from_tzid( tzid, first_date=first_date, last_date=last_date ) except ValueError: continue self.add_component(timezone) calendar_name = multi_language_text_property( "NAME", "X-WR-CALNAME", """This property specifies the name of the calendar. This implements :rfc:`7986` ``NAME`` and ``X-WR-CALNAME``. Property Parameters: IANA, non-standard, alternate text representation, and language property parameters can be specified on this property. Conformance: This property can be specified multiple times in an iCalendar object. However, each property MUST represent the name of the calendar in a different language. Description: This property is used to specify a name of the iCalendar object that can be used by calendar user agents when presenting the calendar data to a user. Whilst a calendar only has a single name, multiple language variants can be specified by including this property multiple times with different "LANGUAGE" parameter values on each. Example: Below, we set the name of the calendar. .. code-block:: pycon >>> from icalendar import Calendar >>> calendar = Calendar() >>> calendar.calendar_name = "My Calendar" >>> print(calendar.to_ical()) BEGIN:VCALENDAR NAME:My Calendar END:VCALENDAR """) description = multi_language_text_property( "DESCRIPTION", "X-WR-CALDESC", """This property specifies the description of the calendar. This implements :rfc:`7986` ``DESCRIPTION`` and ``X-WR-CALDESC``. Conformance: This property can be specified multiple times in an iCalendar object. However, each property MUST represent the description of the calendar in a different language. Description: This property is used to specify a lengthy textual description of the iCalendar object that can be used by calendar user agents when describing the nature of the calendar data to a user. Whilst a calendar only has a single description, multiple language variants can be specified by including this property multiple times with different "LANGUAGE" parameter values on each. Example: Below, we add a description to a calendar. .. code-block:: pycon >>> from icalendar import Calendar >>> calendar = Calendar() >>> calendar.description = "This is a calendar" >>> print(calendar.to_ical()) BEGIN:VCALENDAR DESCRIPTION:This is a calendar END:VCALENDAR """) color = single_string_property( "COLOR", """This property specifies a color used for displaying the calendar. This implements :rfc:`7986` ``COLOR`` and ``X-APPLE-CALENDAR-COLOR``. Please note that since :rfc:`7986`, subcomponents can have their own color. Property Parameters: IANA and non-standard property parameters can be specified on this property. Conformance: This property can be specified once in an iCalendar object or in ``VEVENT``, ``VTODO``, or ``VJOURNAL`` calendar components. Description: This property specifies a color that clients MAY use when presenting the relevant data to a user. Typically, this would appear as the "background" color of events or tasks. The value is a case-insensitive color name taken from the CSS3 set of names, defined in Section 4.3 of `W3C.REC-css3-color-20110607 `_. Example: ``"turquoise"``, ``"#ffffff"`` .. code-block:: pycon >>> from icalendar import Calendar >>> calendar = Calendar() >>> calendar.color = "black" >>> print(calendar.to_ical()) BEGIN:VCALENDAR COLOR:black END:VCALENDAR """, "X-APPLE-CALENDAR-COLOR", ) categories = categories_property uid = uid_property # These are read only singleton, so one instance is enough for the module types_factory = TypesFactory() component_factory = ComponentFactory() __all__ = [ "INLINE", "Alarm", "Calendar", "Component", "ComponentFactory", "Event", "FreeBusy", "IncompleteComponent", "Journal", "Timezone", "TimezoneDaylight", "TimezoneStandard", "Todo", "component_factory", "get_example", ] icalendar-6.3.1/src/icalendar/caselessdict.py000066400000000000000000000067431501302773300212070ustar00rootroot00000000000000from icalendar.parser_tools import to_unicode from collections import OrderedDict 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 = {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(OrderedDict): """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.""" super().__init__(*args, **kwargs) for key, value in self.items(): key_upper = to_unicode(key).upper() if key != key_upper: super().__delitem__(key) self[key_upper] = value def __getitem__(self, key): key = to_unicode(key) return super().__getitem__(key.upper()) def __setitem__(self, key, value): key = to_unicode(key) super().__setitem__(key.upper(), value) def __delitem__(self, key): key = to_unicode(key) super().__delitem__(key.upper()) def __contains__(self, key): key = to_unicode(key) return super().__contains__(key.upper()) def get(self, key, default=None): key = to_unicode(key) return super().get(key.upper(), default) def setdefault(self, key, value=None): key = to_unicode(key) return super().setdefault(key.upper(), value) def pop(self, key, default=None): key = to_unicode(key) return super().pop(key.upper(), default) def popitem(self): return super().popitem() def has_key(self, key): key = to_unicode(key) return super().__contains__(key.upper()) def update(self, *args, **kwargs): # Multiple keys where key1.upper() == key2.upper() will be lost. mappings = list(args) + [kwargs] for mapping in mappings: if hasattr(mapping, "items"): mapping = iter(mapping.items()) for key, value in mapping: self[key] = value def copy(self): return type(self)(super().copy()) def __repr__(self): return f"{type(self).__name__}({dict(self)})" def __eq__(self, other): return self is other or dict(self.items()) == dict(other.items()) def __ne__(self, other): return not self == other # 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) __all__ = ["canonsort_keys", "canonsort_items", "CaselessDict"] icalendar-6.3.1/src/icalendar/cli.py000077500000000000000000000056441501302773300173120ustar00rootroot00000000000000#!/usr/bin/env python3 """utility program that allows user to preview calendar's events""" import sys import pathlib import argparse from datetime import datetime from icalendar import Calendar, __version__ def _format_name(address): """Retrieve the e-mail and the name from an address. :arg an address object, e.g. mailto:test@test.test :returns str: The name and the e-mail address. """ email = address.split(":")[-1] name = email.split("@")[0] if not email: return "" return f"{name} <{email}>" def _format_attendees(attendees): """Format the list of attendees. :arg any attendees: Either a list, a string or a vCalAddress object. :returns str: Formatted list of attendees. """ if isinstance(attendees, str): attendees = [attendees] return "\n".join(map(lambda s: s.rjust(len(s) + 5), map(_format_name, attendees))) def view(event): """Make a human readable summary of an iCalendar file. :returns str: Human readable summary. """ summary = event.get("summary", default="") organizer = _format_name(event.get("organizer", default="")) attendees = _format_attendees(event.get("attendee", default=[])) location = event.get("location", default="") comment = event.get("comment", "") description = event.get("description", "").split("\n") description = "\n".join(map(lambda s: s.rjust(len(s) + 5), description)) start = event.decoded("dtstart") if "duration" in event: end = event.decoded("dtend", default=start + event.decoded("duration")) else: end = event.decoded("dtend", default=start) duration = event.decoded("duration", default=end - start) if isinstance(start, datetime): start = start.astimezone() start = start.strftime("%c") if isinstance(end, datetime): end = end.astimezone() end = end.strftime("%c") return f""" Organizer: {organizer} Attendees: {attendees} Summary : {summary} Starts : {start} End : {end} Duration : {duration} Location : {location} Comment : {comment} Description: {description}""" def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("calendar_files", nargs="+", type=pathlib.Path) parser.add_argument( "--output", "-o", type=argparse.FileType("w"), default=sys.stdout, help="output file", ) parser.add_argument( "-v", "--version", action="version", version=f"{parser.prog} version {__version__}", ) argv = parser.parse_args() for calendar_file in argv.calendar_files: with open(calendar_file, encoding="utf-8-sig") as f: calendar = Calendar.from_ical(f.read()) for event in calendar.walk("vevent"): argv.output.write(view(event) + "\n\n") __all__ = ["main", "view"] if __name__ == "__main__": main() icalendar-6.3.1/src/icalendar/enums.py000066400000000000000000000046011501302773300176570ustar00rootroot00000000000000"""Enumerations for different types in the RFCs.""" from enum import Enum as _Enum class Enum(_Enum): """Enum class that can be pickled.""" def __reduce_ex__(self, _p): """For pickling.""" return self.__class__, (self._name_,) class StrEnum(str, Enum): """Enum for strings.""" class PARTSTAT(StrEnum): """Enum for PARTSTAT from :rfc:`5545`. Attributes: ``NEEDS_ACTION``, ``ACCEPTED``, ``DECLINED``, ``TENTATIVE``, ``DELEGATED``, ``COMPLETED``, ``IN_PROCESS`` """ NEEDS_ACTION = "NEEDS-ACTION" ACCEPTED = "ACCEPTED" DECLINED = "DECLINED" TENTATIVE = "TENTATIVE" DELEGATED = "DELEGATED" COMPLETED = "COMPLETED" IN_PROCESS = "IN-PROCESS" class FBTYPE(StrEnum): """Enum for FBTYPE from :rfc:`5545`. Attributes: ``FREE``, ``BUSY``, ``BUSY-UNAVAILABLE``, ``BUSY-TENTATIVE`` """ FREE = "FREE" BUSY = "BUSY" BUSY_UNAVAILABLE = "BUSY-UNAVAILABLE" BUSY_TENTATIVE = "BUSY-TENTATIVE" class CUTYPE(StrEnum): """Enum for CTYPE from :rfc:`5545`. Attributes: ``INDIVIDUAL``, ``GROUP``, ``RESOURCE``, ``ROOM``, ``UNKNOWN`` """ INDIVIDUAL = "INDIVIDUAL" GROUP = "GROUP" RESOURCE = "RESOURCE" ROOM = "ROOM" UNKNOWN = "UNKNOWN" class RELTYPE(StrEnum): """Enum for RELTYPE from :rfc:`5545`. Attributes: ``PARENT``, ``CHILD``, ``SIBLING`` """ PARENT = "PARENT" CHILD = "CHILD" SIBLING = "SIBLING" class RANGE(StrEnum): """Enum for RANGE from :rfc:`5545`. Attributes: ``THISANDFUTURE``, ``THISANDPRIOR`` """ THISANDFUTURE = "THISANDFUTURE" THISANDPRIOR = "THISANDPRIOR" # deprecated class RELATED(StrEnum): """Enum for RELATED from :rfc:`5545`. Attributes: ``START``, ``END`` """ START = "START" END = "END" class ROLE(StrEnum): """Enum for ROLE from :rfc:`5545`. Attributes: ``CHAIR``, ``REQ-PARTICIPANT``, ``OPT-PARTICIPANT``, ``NON-PARTICIPANT`` """ CHAIR = "CHAIR" REQ_PARTICIPANT = "REQ-PARTICIPANT" OPT_PARTICIPANT = "OPT-PARTICIPANT" NON_PARTICIPANT = "NON-PARTICIPANT" __all__ = [ "PARTSTAT", "FBTYPE", "CUTYPE", "RANGE", "RELATED", "ROLE", "RELTYPE", ] icalendar-6.3.1/src/icalendar/error.py000066400000000000000000000033471501302773300176670ustar00rootroot00000000000000"""Errors thrown by icalendar.""" class InvalidCalendar(ValueError): """The calendar given is not valid. This calendar does not conform with RFC 5545 or breaks other RFCs. """ class IncompleteComponent(ValueError): """The component is missing attributes. The attributes are not required, otherwise this would be an InvalidCalendar. But in order to perform calculations, this attribute is required. This error is not raised in the UPPERCASE properties like .DTSTART, only in the lowercase computations like .start. """ class IncompleteAlarmInformation(ValueError): """The alarms cannot be calculated yet because information is missing.""" class LocalTimezoneMissing(IncompleteAlarmInformation): """We are missing the local timezone to compute the value. Use Alarms.set_local_timezone(). """ class ComponentEndMissing(IncompleteAlarmInformation): """We are missing the end of a component that the alarm is for. Use Alarms.set_end(). """ class ComponentStartMissing(IncompleteAlarmInformation): """We are missing the start of a component that the alarm is for. Use Alarms.set_start(). """ class FeatureWillBeRemovedInFutureVersion(DeprecationWarning): """This feature will be removed in a future version.""" class WillBeRemovedInVersion7(FeatureWillBeRemovedInFutureVersion): """This feature will be removed in icalendar version 7. Suppress FeatureWillBeRemovedInFutureVersion instead. """ __all__ = [ "InvalidCalendar", "IncompleteComponent", "IncompleteAlarmInformation", "LocalTimezoneMissing", "ComponentEndMissing", "ComponentStartMissing", "FeatureWillBeRemovedInFutureVersion", "WillBeRemovedInVersion7", ] icalendar-6.3.1/src/icalendar/fuzzing/000077500000000000000000000000001501302773300176515ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/fuzzing/build.sh000077500000000000000000000015771501302773300213210ustar00rootroot00000000000000#!/bin/bash -eu # Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ################################################################################ cd "$SRC"/icalendar pip3 install . # Build fuzzers in $OUT for fuzzer in $(find src/icalendar/fuzzing -name '*_fuzzer.py');do compile_python_fuzzer "$fuzzer" done zip -q $OUT/ical_fuzzer_seed_corpus.zip $SRC/corpus/* icalendar-6.3.1/src/icalendar/fuzzing/corpus/000077500000000000000000000000001501302773300211645ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/fuzzing/corpus/Index_Error.ics000066400000000000000000000000551501302773300241040ustar00rootroot00000000000000!bEGN: END:‪!bEGN: END:ใVT󠁒IOEEicalendar-6.3.1/src/icalendar/fuzzing/corpus/Type_Error.ics000066400000000000000000000000571501302773300237600ustar00rootroot00000000000000(EBEGIN: U: TJDATEBEGIN:VEV: RDATE:0 ENd:icalendar-6.3.1/src/icalendar/fuzzing/corpus/america_new_york.ics000066400000000000000000000025361501302773300252100ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VTIMEZONE TZID:custom_America/New_York LAST-MODIFIED:20050809T050000Z BEGIN:DAYLIGHT DTSTART:19670430T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19730429T070000Z TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:STANDARD DTSTART:19671029T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=20061029T060000Z TZOFFSETFROM:-0400 TZOFFSETTO:-0500 TZNAME:EST END:STANDARD BEGIN:DAYLIGHT DTSTART:19740106T020000 RDATE:19750223T020000 TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19760425T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19860427T070000Z TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19870405T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU;UNTIL=20060402T070000Z TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU TZOFFSETFROM:-0400 TZOFFSETTO:-0500 TZNAME:EST END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:noend123 DTSTART;TZID=custom_America/New_York;VALUE=DATE-TIME:20140829T080000 DTSTART;TZID=custom_America/New_York;VALUE=DATE-TIME:20140829T100000 SUMMARY:an event with a custom tz name END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/fuzzing/corpus/calendar_with_unicode.ics000066400000000000000000000003061501302773300261750ustar00rootroot00000000000000BEGIN: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 END:VCALENDAR icalendar-6.3.1/src/icalendar/fuzzing/corpus/event_with_escaped_character1.ics000066400000000000000000000000741501302773300276220ustar00rootroot00000000000000BEGIN:VEVENT ORGANIZER;CN=Society\, 2014:that END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/event_with_escaped_character2.ics000066400000000000000000000000741501302773300276230ustar00rootroot00000000000000BEGIN:VEVENT ORGANIZER;CN=Society\\ 2014:that END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/event_with_escaped_character3.ics000066400000000000000000000000741501302773300276240ustar00rootroot00000000000000BEGIN:VEVENT ORGANIZER;CN=Society\; 2014:that END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/event_with_escaped_character4.ics000066400000000000000000000000741501302773300276250ustar00rootroot00000000000000BEGIN:VEVENT ORGANIZER;CN=Society\: 2014:that END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/event_with_escaped_characters.ics000066400000000000000000000001501501302773300277170ustar00rootroot00000000000000BEGIN:VEVENT ORGANIZER;CN=that\, that\; %th%%at%\ that\::это\, то\; that\ %th%%at%\: END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/event_with_recurrence.ics000066400000000000000000000003101501302773300262470ustar00rootroot00000000000000BEGIN:VEVENT DTSTART:19960401T010000 DTEND:19960401T020000 RRULE:FREQ=DAILY;COUNT=100 EXDATE:19960402T010000Z,19960403T010000Z,19960404T010000Z SUMMARY:A recurring event with exdates END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/event_with_recurrence_exdates_on_different_lines.ics000066400000000000000000000007101501302773300337040ustar00rootroot00000000000000BEGIN: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 icalendar-6.3.1/src/icalendar/fuzzing/corpus/event_with_rsvp.ics000066400000000000000000000001111501302773300251030ustar00rootroot00000000000000BEGIN:VEVENT ATTENDEE;RSVP=TRUE:mailto:someone@example.com END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/event_with_unicode_fields.ics000066400000000000000000000003761501302773300271020ustar00rootroot00000000000000BEGIN: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 icalendar-6.3.1/src/icalendar/fuzzing/corpus/event_with_unicode_organizer.ics000066400000000000000000000001261501302773300276250ustar00rootroot00000000000000BEGIN:VEVENT ORGANIZER;CN="Джон Доу":mailto:john.doe@example.org END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_100_transformed_doctests_into_unittests.ics000066400000000000000000000000651501302773300330640ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY;LANGUAGE=ru:te END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_101_icalendar_chokes_on_umlauts_in_organizer.ics000066400000000000000000000005471501302773300337550ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY:wichtiger termin 1 DTSTART:20130416T100000Z DTEND:20130416T110000Z DTSTAMP:20130416T092616Z UID:20130416112341.10064jz0k4j7uem8@acmenet.de CLASS:PUBLIC CREATED:20130416T092341Z LAST-MODIFIED:20130416T092341Z LOCATION:im büro ORGANIZER;CN="acme, ädmin":mailto:adm-acme@mydomain.de STATUS:CONFIRMED TRANSP:OPAQUE END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_104_mark_events_broken.ics000066400000000000000000000002441501302773300273360ustar00rootroot00000000000000BEGIN:VEVENT DTSTART:20140401T000000Z DTEND:20140401T010000Z DTSTAMP:20140401T000000Z SUMMARY:Broken Eevnt CLASS:PUBLIC STATUS:CONFIRMED TRANSP:OPAQUE X END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_112_missing_tzinfo_on_exdate.ics000066400000000000000000000012361501302773300305510ustar00rootroot00000000000000BEGIN: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 icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_156_RDATE_with_PERIOD.ics000066400000000000000000000002521501302773300264220ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY:RDATE period DTSTART:19961230T020000Z DTEND:19961230T060000Z UID:rdate_period RDATE;VALUE=PERIOD:19970101T180000Z/19970102T070000Z END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_156_RDATE_with_PERIOD_list.ics000066400000000000000000000003061501302773300274550ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY:RDATE period DTSTART:19961230T020000Z DTEND:19961230T060000Z UID:rdate_period RDATE;VALUE=PERIOD:19970101T180000Z/19970102T070000Z,19970109T180000Z/PT5H 30M END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_157_removes_trailing_semicolon.ics000066400000000000000000000001341501302773300311070ustar00rootroot00000000000000BEGIN:VEVENT DTSTART:20150325T101010 RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU; END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_184_broken_representation_of_period.ics000066400000000000000000000002031501302773300321130ustar00rootroot00000000000000BEGIN:VEVENT DTSTART:20150219T133000 DTSTAMP:20150219T133000 UID:1234567 RDATE;VALUE=PERIOD:20150219T133000/PT10H END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_464_invalid_rdate.ics000066400000000000000000000003011501302773300262700ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY:RDATE period DTSTART:19961230T020000Z DTEND:19961230T060000Z UID:rdate_period RDATE;VALUE=PERIOD:19970101T180000Z/19970102T070000Z,199709T180000Z/PT5H30M END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_53_description_parsed_properly.ics000066400000000000000000000013431501302773300312210ustar00rootroot00000000000000BEGIN: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 icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_64_event_with_ascii_summary.ics000066400000000000000000000000521501302773300305030ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY:abcdef END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_64_event_with_non_ascii_summary.ics000066400000000000000000000000521501302773300313550ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY:åäö END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_70_rrule_causes_attribute_error.ics000066400000000000000000000004271501302773300313750ustar00rootroot00000000000000BEGIN: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 icalendar-6.3.1/src/icalendar/fuzzing/corpus/issue_82_expected_output.ics000066400000000000000000000001331501302773300266230ustar00rootroot00000000000000BEGIN:VEVENT ATTACH;ENCODING=BASE64;FMTTYPE=text/plain;VALUE=BINARY:dGV4dA== END:VEVENT icalendar-6.3.1/src/icalendar/fuzzing/corpus/pacific_fiji.ics000066400000000000000000000023061501302773300242640ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//tzurl.org//NONSGML Olson 2014g//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:custom_Pacific/Fiji TZURL:http://tzurl.org/zoneinfo/Pacific/Fiji X-LIC-LOCATION:Pacific/Fiji BEGIN:DAYLIGHT TZOFFSETFROM:+1200 TZOFFSETTO:+1300 DTSTART:20101024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYMONTHDAY=21,22,23,24,25,26,27;BYDAY=SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+1300 TZOFFSETTO:+1200 DTSTART:20140119T020000 RRULE:FREQ=YEARLY;BYMONTH=1;BYMONTHDAY=18,19,20,21,22,23,24;BYDAY=SU END:STANDARD BEGIN:STANDARD TZOFFSETFROM:+115544 TZOFFSETTO:+1200 DTSTART:19151026T000000 RDATE:19151026T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:+1200 TZOFFSETTO:+1300 DTSTART:19981101T020000 RDATE:19981101T020000 RDATE:19991107T020000 RDATE:20091129T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+1300 TZOFFSETTO:+1200 DTSTART:19990228T030000 RDATE:19990228T030000 RDATE:20000227T030000 RDATE:20100328T030000 RDATE:20110306T030000 RDATE:20120122T030000 RDATE:20130120T030000 END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:noend123 DTSTART;TZID=custom_Pacific/Fiji;VALUE=DATE-TIME:20140829T080000 DTSTART;TZID=custom_Pacific/Fiji;VALUE=DATE-TIME:20140829T100000 SUMMARY:an event with a custom tz name END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/fuzzing/corpus/time.ics000066400000000000000000000000731501302773300226220ustar00rootroot00000000000000BEGIN:VCALENDAR X-SOMETIME;VALUE=TIME:172010 END:VCALENDAR icalendar-6.3.1/src/icalendar/fuzzing/corpus/timezone_rdate.ics000066400000000000000000000021251501302773300246750ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 BEGIN:VTIMEZONE TZID:posix/Europe/Vaduz BEGIN:STANDARD TZNAME:CET TZOFFSETFROM:+002946 TZOFFSETTO:+0100 DTSTART:19011213T211538 RDATE;VALUE=DATE-TIME:19011213T211538 END:STANDARD BEGIN:DAYLIGHT TZNAME:CEST TZOFFSETFROM:+0100 TZOFFSETTO:+0200 DTSTART:19810329T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 END:DAYLIGHT BEGIN:DAYLIGHT TZNAME:CEST TZOFFSETFROM:+0100 TZOFFSETTO:+0200 DTSTART:19410505T010000 RDATE;VALUE=DATE-TIME:19410505T010000 RDATE;VALUE=DATE-TIME:19420504T010000 END:DAYLIGHT BEGIN:STANDARD TZNAME:CET TZOFFSETFROM:+0200 TZOFFSETTO:+0100 DTSTART:19810927T030000 RRULE:FREQ=YEARLY;COUNT=15;BYDAY=-1SU;BYMONTH=9 END:STANDARD BEGIN:STANDARD TZNAME:CET TZOFFSETFROM:+0200 TZOFFSETTO:+0100 DTSTART:19961027T030000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 END:STANDARD BEGIN:STANDARD TZNAME:CET TZOFFSETFROM:+0200 TZOFFSETTO:+0100 DTSTART:19411006T020000 RDATE;VALUE=DATE-TIME:19411006T020000 RDATE;VALUE=DATE-TIME:19421005T020000 END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:123 DTSTART;TZID=posix/Europe/Vaduz:20120213T100000 SUMMARY=testevent END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/fuzzing/corpus/timezone_same_start.ics000066400000000000000000000012551501302773300257430ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:Microsoft Exchange Server 2010 METHOD:REQUEST BEGIN:VTIMEZONE TZID:Pacific Standard Time BEGIN:STANDARD DTSTART:16010101T020000 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11 TZOFFSETFROM:-0700 TZOFFSETTO:-0800 END:STANDARD BEGIN:DAYLIGHT DTSTART:16010101T020000 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3 TZOFFSETFROM:-0800 TZOFFSETTO:-0700 END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT SUMMARY;LANGUAGE=en-US:Test 4 DTSTART;TZID="Pacific Standard Time":20170224T120000 DTEND;TZID="Pacific Standard Time":20170224T123000 DTSTAMP:20170224T180431Z UID:040000008200E00074C5B7101A82E0080000000090E19664858ED20100000000000000 END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/fuzzing/corpus/timezone_same_start_and_offset.ics000066400000000000000000000007331501302773300301330ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:Microsoft Exchange Server 2010 BEGIN:VTIMEZONE TZID:Tokyo Standard Time BEGIN:STANDARD DTSTART:16010101T000000 TZOFFSETFROM:+0900 TZOFFSETTO:+0900 END:STANDARD BEGIN:DAYLIGHT DTSTART:16010101T000000 TZOFFSETFROM:+0900 TZOFFSETTO:+0900 END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT DTSTART;TZID="Tokyo Standard Time":20170224T120000 DTEND;TZID="Tokyo Standard Time":20170224T123000 UID:blafoobar SUMMARY:this is an event END:VEVENT END:VCALENDARD icalendar-6.3.1/src/icalendar/fuzzing/corpus/timezoned.ics000066400000000000000000000014541501302773300236660ustar00rootroot00000000000000BEGIN: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-6.3.1/src/icalendar/fuzzing/ical_fuzzer.py000066400000000000000000000044731501302773300225500ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ################################################################################ import atheris import sys import base64 with atheris.instrument_imports(): import icalendar from icalendar.tests.fuzzed import fuzz_calendar_v1 _value_error_matches = [ "component", "parse", "Expected", "Wrong date format", "END encountered", "vDDD", "recurrence", "Offset must", "Invalid iCalendar", "alue MUST", "Key name", "Invalid content line", "does not exist", "base 64", "must use datetime", "Unknown date type", "Wrong", "Start time", "iCalendar", "recurrence", "float, float", "utc offset", "parent", "MUST be a datetime", ] @atheris.instrument_func def TestOneInput(data): fdp = atheris.FuzzedDataProvider(data) try: multiple = fdp.ConsumeBool() should_walk = fdp.ConsumeBool() calendar_string = fdp.ConsumeString(fdp.remaining_bytes()) print("--- start calendar ---") try: # print the ICS file for the test case extraction # see https://stackoverflow.com/a/27367173/1320237 print( base64.b64encode( calendar_string.encode("UTF-8", "surrogateescape") ).decode("ASCII") ) except UnicodeEncodeError: pass print("--- end calendar ---") fuzz_calendar_v1( icalendar.Calendar.from_ical, calendar_string, multiple, should_walk ) except ValueError as e: if any(m in str(e) for m in _value_error_matches): return -1 raise e def main(): atheris.Setup(sys.argv, TestOneInput) atheris.Fuzz() if __name__ == "__main__": main() icalendar-6.3.1/src/icalendar/param.py000066400000000000000000000412641501302773300176360ustar00rootroot00000000000000"""Parameter access for icalendar. Related: - :rfc:`5545`, Section 3.2. Property Parameters - :rfc:`7986`, Section 6. Property Parameters - https://github.com/collective/icalendar/issues/798 """ from __future__ import annotations import functools from typing import TYPE_CHECKING, Callable, Optional, TypeVar, Union from icalendar import enums if TYPE_CHECKING: from enum import Enum from icalendar.parser import Parameters class IcalendarProperty: """Interface provided by properties in icalendar.prop.""" params: Parameters def _default_return_none() -> Optional[str]: """Return None by default.""" return None def _default_return_string() -> str: """Return None by default.""" return "" T = TypeVar("T") def string_parameter( name:str, doc:str, default:Callable = _default_return_none, convert:Optional[Callable[[str], T]] = None, convert_to:Optional[Callable[[T], str]] = None ) -> property: """Return a parameter with a quoted value (case sensitive).""" if convert_to is None: convert_to = convert @functools.wraps(default) def fget(self: IcalendarProperty) -> Optional[str]: value = self.params.get(name) if value is None: return default() return convert(value) if convert else value def fset(self: IcalendarProperty, value: str): self.params[name] = convert_to(value) if convert_to else value def fdel(self: IcalendarProperty): self.params.pop(name, None) return property(fget, fset, fdel, doc=doc) ALTREP = string_parameter( "ALTREP", """ALTREP - Specify an alternate text representation for the property value. Description: This parameter specifies a URI that points to an alternate representation for a textual property value. A property specifying this parameter MUST also include a value that reflects the default representation of the text value. The URI parameter value MUST be specified in a quoted-string. .. note:: While there is no restriction imposed on the URI schemes allowed for this parameter, Content Identifier (CID) :rfc:`2392`, HTTP :rfc:`2616`, and HTTPS :rfc:`2818` are the URI schemes most commonly used by current implementations. """) CN = string_parameter( "CN", """Specify the common name to be associated with the calendar user specified. Description: This parameter can be specified on properties with a CAL-ADDRESS value type. The parameter specifies the common name to be associated with the calendar user specified by the property. The parameter value is text. The parameter value can be used for display text to be associated with the calendar address specified by the property. """, default=_default_return_string ) def _default_return_individual() -> enums.CUTYPE|str: """Default value.""" return enums.CUTYPE.INDIVIDUAL def _convert_enum(enum: type[Enum]) -> Callable[[str], Enum]: def convert(value: str) -> str: """Convert if possible.""" try: return enum(value.upper()) except ValueError: return value return convert CUTYPE = string_parameter( "CUTYPE", """Identify the type of calendar user specified by the property. Description: This parameter can be specified on properties with a CAL-ADDRESS value type. The parameter identifies the type of calendar user specified by the property. If not specified on a property that allows this parameter, the default is INDIVIDUAL. Applications MUST treat x-name and iana-token values they don't recognize the same way as they would the UNKNOWN value. """, default=_default_return_individual, convert=_convert_enum(enums.CUTYPE)) def quoted_list_parameter(name: str, doc: str) -> property: """Return a parameter that contains a quoted list.""" def fget(self: IcalendarProperty) -> tuple[str]: value = self.params.get(name) if value is None: return () if isinstance(value, str): return tuple(value.split(",")) return value def fset(self: IcalendarProperty, value: str|tuple[str]): if value == (): fdel(self) else: self.params[name] = (value,) if isinstance(value, str) else value def fdel(self: IcalendarProperty): self.params.pop(name, None) return property(fget, fset, fdel, doc=doc) DELEGATED_FROM = quoted_list_parameter( "DELEGATED-FROM", """Specify the calendar users that have delegated their participation to the calendar user specified by the property. Description: This parameter can be specified on properties with a CAL-ADDRESS value type. This parameter specifies those calendar users that have delegated their participation in a group-scheduled event or to-do to the calendar user specified by the property. The individual calendar address parameter values MUST each be specified in a quoted-string. """) DELEGATED_TO = quoted_list_parameter( "DELEGATED-TO", """Specify the calendar users to whom the calendar user specified by the property has delegated participation. Description: This parameter can be specified on properties with a CAL-ADDRESS value type. This parameter specifies those calendar users whom have been delegated participation in a group-scheduled event or to-do by the calendar user specified by the property. The individual calendar address parameter values MUST each be specified in a quoted-string. """) DIR = string_parameter( "DIR", """Specify reference to a directory entry associated with the calendar user specified by the property. Description: This parameter can be specified on properties with a CAL-ADDRESS value type. The parameter specifies a reference to the directory entry associated with the calendar user specified by the property. The parameter value is a URI. The URI parameter value MUST be specified in a quoted-string. .. note:: While there is no restriction imposed on the URI schemes allowed for this parameter, CID :rfc:`2392`, DATA :rfc:`2397`, FILE :rfc:`1738`, FTP :rfc:`1738`, HTTP :rfc:`2616`, HTTPS :rfc:`2818`, LDAP :rfc:`4516`, and MID :rfc:`2392` are the URI schemes most commonly used by current implementations. """) def _default_return_busy() -> enums.FBTYPE|str: """Default value.""" return enums.FBTYPE.BUSY FBTYPE = string_parameter( "FBTYPE", """Specify the free or busy time type. Description: This parameter specifies the free or busy time type. The value FREE indicates that the time interval is free for scheduling. The value BUSY indicates that the time interval is busy because one or more events have been scheduled for that interval. The value BUSY-UNAVAILABLE indicates that the time interval is busy and that the interval can not be scheduled. The value BUSY-TENTATIVE indicates that the time interval is busy because one or more events have been tentatively scheduled for that interval. If not specified on a property that allows this parameter, the default is BUSY. Applications MUST treat x-name and iana-token values they don't recognize the same way as they would the BUSY value. """, default=_default_return_busy, convert=_convert_enum(enums.FBTYPE)) LANGUAGE = string_parameter( "LANGUAGE", """Specify the language for text values in a property or property parameter. Description: This parameter identifies the language of the text in the property value and of all property parameter values of the property. The value of the "LANGUAGE" property parameter is that defined in :rfc:`5646`. For transport in a MIME entity, the Content-Language header field can be used to set the default language for the entire body part. Otherwise, no default language is assumed. """) MEMBER = quoted_list_parameter( "MEMBER", """Specify the group or list membership of the calendar user specified by the property. Description: This parameter can be specified on properties with a CAL-ADDRESS value type. The parameter identifies the groups or list membership for the calendar user specified by the property. The parameter value is either a single calendar address in a quoted-string or a COMMA-separated list of calendar addresses, each in a quoted-string. The individual calendar address parameter values MUST each be specified in a quoted-string. """ ) def _default_return_needs_action() -> enums.PARTSTAT|str: """Default value.""" return enums.PARTSTAT.NEEDS_ACTION PARTSTAT = string_parameter( "PARTSTAT", """Specify the participation status for the calendar user specified by the property. Description: This parameter can be specified on properties with a CAL-ADDRESS value type. The parameter identifies the participation status for the calendar user specified by the property value. The parameter values differ depending on whether they are associated with a group-scheduled "VEVENT", "VTODO", or "VJOURNAL". The values MUST match one of the values allowed for the given calendar component. If not specified on a property that allows this parameter, the default value is NEEDS-ACTION. Applications MUST treat x-name and iana-token values they don't recognize the same way as they would the NEEDS-ACTION value. """, default=_default_return_needs_action, convert=_convert_enum(enums.PARTSTAT)) def _default_range_none() -> Optional[enums.RANGE|str]: return None RANGE = string_parameter( "RANGE", """Specify the effective range of recurrence instances from the instance specified by the recurrence identifier specified by the property. Description: This parameter can be specified on a property that specifies a recurrence identifier. The parameter specifies the effective range of recurrence instances that is specified by the property. The effective range is from the recurrence identifier specified by the property. If this parameter is not specified on an allowed property, then the default range is the single instance specified by the recurrence identifier value of the property. The parameter value can only be "THISANDFUTURE" to indicate a range defined by the recurrence identifier and all subsequent instances. The value "THISANDPRIOR" is deprecated by this revision of iCalendar and MUST NOT be generated by applications. """, default=_default_range_none, convert=_convert_enum(enums.RANGE)) def _default_related() -> enums.RELATED|str: return enums.RELATED.START RELATED = string_parameter( "RELATED", """Specify the relationship of the alarm trigger with respect to the start or end of the calendar component. Description: This parameter can be specified on properties that specify an alarm trigger with a "DURATION" value type. The parameter specifies whether the alarm will trigger relative to the start or end of the calendar component. The parameter value START will set the alarm to trigger off the start of the calendar component; the parameter value END will set the alarm to trigger off the end of the calendar component. If the parameter is not specified on an allowable property, then the default is START. """, default=_default_related, convert=_convert_enum(enums.RANGE)) def _default_req_participant() -> enums.ROLE|str: return enums.ROLE.REQ_PARTICIPANT ROLE = string_parameter( "ROLE", """Specify the participation role for the calendar user specified by the property. Description: This parameter can be specified on properties with a CAL-ADDRESS value type. The parameter specifies the participation role for the calendar user specified by the property in the group schedule calendar component. If not specified on a property that allows this parameter, the default value is REQ-PARTICIPANT. Applications MUST treat x-name and iana-token values they don't recognize the same way as they would the REQ-PARTICIPANT value. """, default=_default_req_participant, convert=_convert_enum(enums.ROLE) ) def boolean_parameter(name: str, default:bool, doc: str) -> property: def _default() -> bool: return default return string_parameter( name, doc, default=_default, convert=lambda x: x.upper() == "TRUE", convert_to=lambda x: "TRUE" if x else "FALSE", ) RSVP = boolean_parameter( "RSVP", False, """Specify whether there is an expectation of a favor of anreply from the calendar user specified by the property value. Description: This parameter can be specified on properties with a CAL-ADDRESS value type. The parameter identifies the expectation of a reply from the calendar user specified by the property value. This parameter is used by the "Organizer" to request a participation status reply from an "Attendee" of a group-scheduled event or to-do. If not specified on a property that allows this parameter, the default value is ``False``. """ ) SENT_BY = string_parameter( "SENT-BY", """Specify the calendar user that is acting on behalf of the calendar user specified by the property. Description: This parameter can be specified on properties with a CAL-ADDRESS value type. The parameter specifies the calendar user that is acting on behalf of the calendar user specified by the property. The parameter value MUST be a mailto URI as defined in :rfc:`2368`. The individual calendar address parameter values MUST each be specified in a quoted-string. """ ) TZID = string_parameter( "TZID", """Specify the identifier for the time zone definition for a time component in the property value. Description: This parameter MUST be specified on the "DTSTART", "DTEND", "DUE", "EXDATE", and "RDATE" properties when either a DATE-TIME or TIME value type is specified and when the value is neither a UTC or a "floating" time. Refer to the DATE-TIME or TIME value type definition for a description of UTC and "floating time" formats. This property parameter specifies a text value that uniquely identifies the "VTIMEZONE" calendar component to be used when evaluating the time portion of the property. The value of the "TZID" property parameter will be equal to the value of the "TZID" property for the matching time zone definition. An individual "VTIMEZONE" calendar component MUST be specified for each unique "TZID" parameter value specified in the iCalendar object. The parameter MUST be specified on properties with a DATE-TIME value if the DATE-TIME is not either a UTC or a "floating" time. Failure to include and follow VTIMEZONE definitions in iCalendar objects may lead to inconsistent understanding of the local time at any given location. The presence of the SOLIDUS character as a prefix, indicates that this "TZID" represents a unique ID in a globally defined time zone registry (when such registry is defined). .. note:: This document does not define a naming convention for time zone identifiers. Implementers may want to use the naming conventions defined in existing time zone specifications such as the public-domain TZ database (TZDB). The specification of globally unique time zone identifiers is not addressed by this document and is left for future study. """ ) def _default_return_parent() -> enums.RELTYPE: return enums.RELTYPE.PARENT RELTYPE = string_parameter( "RELTYPE", """Specify the type of hierarchical relationship associated with the calendar component specified by the property. Description: This parameter can be specified on a property that references another related calendar. The parameter specifies the hierarchical relationship type of the calendar component referenced by the property. The parameter value can be PARENT, to indicate that the referenced calendar component is a superior of calendar component; CHILD to indicate that the referenced calendar component is a subordinate of the calendar component; or SIBLING to indicate that the referenced calendar component is a peer of the calendar component. If this parameter is not specified on an allowable property, the default relationship type is PARENT. Applications MUST treat x-name and iana-token values they don't recognize the same way as they would the PARENT value. """, default=_default_return_parent, convert=_convert_enum(enums.RELTYPE)) __all__ = [ "string_parameter", "quoted_list_parameter", "ALTREP", "CN", "CUTYPE", "DELEGATED_FROM", "DELEGATED_TO", "DIR", "FBTYPE", "LANGUAGE", "MEMBER", "PARTSTAT", "RANGE", "RELATED", "ROLE", "RSVP", "SENT_BY", "TZID", ] icalendar-6.3.1/src/icalendar/parser.py000066400000000000000000000352601501302773300200310ustar00rootroot00000000000000"""This module parses and generates contentlines as defined in RFC 5545 (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 __future__ import annotations import os from icalendar.caselessdict import CaselessDict from icalendar.parser_tools import DEFAULT_ENCODING, ICAL_TYPE from icalendar.parser_tools import SEQUENCE_TYPES from icalendar.parser_tools import to_unicode import re def escape_char(text): """Format value according to iCalendar TEXT escaping rules.""" assert isinstance(text, (str, bytes)) # 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, (str, bytes)) # NOTE: ORDER MATTERS! if isinstance(text, str): return ( text.replace("\\N", "\\n") .replace("\r\n", "\n") .replace("\\n", "\n") .replace("\\,", ",") .replace("\\;", ";") .replace("\\\\", "\\") ) elif isinstance(text, bytes): 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 foldline(line, limit=75, fold_sep="\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, str) assert "\n" not in line # Use a fast and simple variant for the common case that line is all ASCII. try: line.encode("ascii") except (UnicodeEncodeError, UnicodeDecodeError): pass else: return fold_sep.join( line[i : i + limit - 1] for i in range(0, len(line), limit - 1) ) ret_chars = [] 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_chars.append(fold_sep) byte_count = char_byte_len ret_chars.append(char) return "".join(ret_chars) ################################################################# # Property parameter stuff def param_value(value, always_quote=False): """Returns a parameter value.""" if isinstance(value, SEQUENCE_TYPES): return q_join(map(rfc_6868_escape, value), always_quote=always_quote) if isinstance(value, str): return dquote(rfc_6868_escape(value), always_quote=always_quote) return dquote(rfc_6868_escape(value.to_ical().decode(DEFAULT_ENCODING))) # Could be improved # [\w-] because of the iCalendar RFC # . because of the vCard RFC NAME = re.compile(r"[\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("(\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, always_quote=False): """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) or always_quote: return f'"{val}"' return val # parsing helper def q_split(st, sep=",", maxsplit=-1): """Splits a string on char, taking double (q)uotes into considderation.""" if maxsplit == 0: return [st] result = [] cursor = 0 length = len(st) inquote = 0 splits = 0 for i, ch in enumerate(st): if ch == '"': inquote = not inquote if not inquote and ch == sep: result.append(st[cursor:i]) cursor = i + 1 splits += 1 if i + 1 == length or splits == maxsplit: result.append(st[cursor:]) break return result def q_join(lst, sep=",", always_quote=False): """Joins a list on sep, quoting strings with QUOTABLE chars.""" return sep.join(dquote(itm, always_quote=always_quote) 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. """ # The following paremeters must always be enclosed in double quotes always_quoted = ( "ALTREP", "DELEGATED-FROM", "DELEGATED-TO", "DIR", "MEMBER", "SENT-BY", # Part of X-APPLE-STRUCTURED-LOCATION "X-ADDRESS", "X-TITLE", ) # this is quoted should one of the values be present quote_also = { # This is escaped in the RFC "CN" : " '", } def params(self): """In RFC 5545 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 to_ical(self, sorted=True): result = [] items = list(self.items()) if sorted: items.sort() for key, value in items: upper_key = key.upper() check_quoteable_characters = self.quote_also.get(key.upper()) always_quote = ( upper_key in self.always_quoted or ( check_quoteable_characters and any(c in value for c in check_quoteable_characters) ) ) quoted_value = param_value(value, always_quote=always_quote) if isinstance(quoted_value, str): quoted_value = quoted_value.encode(DEFAULT_ENCODING) # CaselessDict keys are always unicode result.append(upper_key.encode(DEFAULT_ENCODING) + b"=" + quoted_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, "=", maxsplit=1) 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(rfc_6868_unescape(v)) else: validate_param_value(v, quoted=False) if strict: vals.append(rfc_6868_unescape(v.upper())) else: vals.append(rfc_6868_unescape(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(f"{param!r} is not a valid parameter string: {exc}") return result def escape_string(val): # f'{i:02X}' return ( val.replace(r"\,", "%2C") .replace(r"\:", "%3A") .replace(r"\;", "%3B") .replace(r"\\", "%5C") ) def unescape_string(val): return ( val.replace("%2C", ",") .replace("%3A", ":") .replace("%3B", ";") .replace("%5C", "\\") ) RFC_6868_UNESCAPE_REGEX = re.compile(r"\^\^|\^n|\^'") def rfc_6868_unescape(param_value: str) -> str: """Take care of :rfc:`6868` unescaping. - ^^ -> ^ - ^n -> system specific newline - ^' -> " - ^ with others stay intact """ replacements = { "^^": "^", "^n": os.linesep, "^'": '"', } return RFC_6868_UNESCAPE_REGEX.sub(lambda m: replacements.get(m.group(0), m.group(0)), param_value) RFC_6868_ESCAPE_REGEX = re.compile(r'\^|\r\n|\r|\n|"') def rfc_6868_escape(param_value: str) -> str: """Take care of :rfc:`6868` escaping. - ^ -> ^^ - " -> ^' - newline -> ^n """ replacements = { "^": "^^", "\n": "^n", "\r": "^n", "\r\n": "^n", '"': "^'", } return RFC_6868_ESCAPE_REGEX.sub(lambda m: replacements.get(m.group(0), m.group(0)), param_value) def unescape_list_or_string(val): if isinstance(val, list): return [unescape_string(s) for s in val] else: return unescape_string(val) ######################################### # parsing and generation of content lines class Contentline(str): """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 "\n" not in value, ( "Content line can not contain unescaped " "new line characters." ) self = super().__new__(cls, value) self.strict = strict return self @classmethod def from_parts(cls, name:ICAL_TYPE, params: Parameters, values, sorted=True): """Turn a parts into a content line.""" assert isinstance(params, Parameters) if hasattr(values, "to_ical"): values = values.to_ical() else: from icalendar.prop import vText 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(sorted=sorted)) return cls(f"{name};{params}:{values}") return cls(f"{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 = unescape_string(st[:name_split]) if not name: raise ValueError("Key name is required") validate_token(name) if not value_split: value_split = i + 1 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( (unescape_string(key), unescape_list_or_string(value)) for key, value in iter(params.items()) ) values = unescape_string(st[value_split + 1 :]) return (name, params, values) except ValueError as exc: raise ValueError( f"Content line could not be parsed into parts: '{self}': {exc}" ) from 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 NEWLINE.split(unfolded) if line) lines.append("") # '\r\n' at the end of every content line return lines except Exception: raise ValueError("Expected StringType with content lines") __all__ = [ "Contentline", "Contentlines", "FOLD", "NAME", "NEWLINE", "Parameters", "QUNSAFE_CHAR", "QUOTABLE", "UNSAFE_CHAR", "dquote", "escape_char", "escape_string", "foldline", "param_value", "q_join", "q_split", "rfc_6868_escape", "rfc_6868_unescape", "uFOLD", "unescape_char", "unescape_list_or_string", "unescape_string", "validate_param_value", "validate_token", ] icalendar-6.3.1/src/icalendar/parser_tools.py000066400000000000000000000035131501302773300212450ustar00rootroot00000000000000from typing import List, Union SEQUENCE_TYPES = (list, tuple) DEFAULT_ENCODING = "utf-8" ICAL_TYPE = Union[str, bytes] def from_unicode(value: ICAL_TYPE, encoding="utf-8") -> bytes: """ Converts a value to bytes, even if it already is bytes :param value: The value to convert :param encoding: The encoding to use in the conversion :return: The bytes representation of the value """ if isinstance(value, bytes): return value elif isinstance(value, str): try: return value.encode(encoding) except UnicodeEncodeError: return value.encode("utf-8", "replace") else: return value def to_unicode(value: ICAL_TYPE, encoding="utf-8-sig") -> str: """Converts a value to unicode, even if it is already a unicode string.""" if isinstance(value, str): return value elif isinstance(value, bytes): try: return value.decode(encoding) except UnicodeDecodeError: return value.decode("utf-8-sig", "replace") else: return value def data_encode( data: Union[ICAL_TYPE, dict, list], encoding=DEFAULT_ENCODING ) -> Union[bytes, List[bytes], dict]: """Encode all datastructures to the given encoding. Currently unicode strings, dicts and lists are supported. """ # https://stackoverflow.com/questions/1254454/fastest-way-to-convert-a-dicts-keys-values-from-unicode-to-str if isinstance(data, str): return data.encode(encoding) elif isinstance(data, dict): return dict(map(data_encode, iter(data.items()))) elif isinstance(data, list) or isinstance(data, tuple): return list(map(data_encode, data)) else: return data __all__ = [ "DEFAULT_ENCODING", "SEQUENCE_TYPES", "ICAL_TYPE", "data_encode", "from_unicode", "to_unicode", ] icalendar-6.3.1/src/icalendar/prop.py000066400000000000000000001720271501302773300175200ustar00rootroot00000000000000"""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: .. code-block:: text 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: .. code-block:: text BINARY, BOOLEAN, CAL-ADDRESS, DATE, DATE-TIME, DURATION, FLOAT, INTEGER, PERIOD, RECUR, TEXT, TIME, URI, UTC-OFFSET ########################################################################### iCalendar properties have values. The values are strongly typed. This module defines these types, calling val.to_ical() on them will render them as defined in rfc5545. 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 start 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 always be true that: .. code-block:: python 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 __future__ import annotations import base64 import binascii import re from datetime import date, datetime, time, timedelta from typing import Union from icalendar.caselessdict import CaselessDict from icalendar.enums import Enum from icalendar.parser import Parameters, escape_char, unescape_char from icalendar.parser_tools import ( DEFAULT_ENCODING, ICAL_TYPE, SEQUENCE_TYPES, from_unicode, to_unicode, ) from .timezone import tzid_from_dt, tzid_from_tzinfo, tzp DURATION_REGEX = re.compile( r"([-+]?)P(?:(\d+)W)?(?:(\d+)D)?" r"(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$" ) WEEKDAY_RULE = re.compile( r"(?P[+-]?)(?P[\d]{0,2})" r"(?P[\w]{2})$" ) class vBinary: """Binary property values are base 64 encoded.""" params: Parameters def __init__(self, obj): self.obj = to_unicode(obj) self.params = Parameters(encoding="BASE64", value="BINARY") def __repr__(self): return f"vBinary({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 (ValueError, UnicodeError): raise ValueError("Not valid base 64 encoding.") def __eq__(self, other): """self == other""" return isinstance(other, vBinary) and self.obj == other.obj class vBoolean(int): """Boolean Value Name: BOOLEAN Purpose: This value type is used to identify properties that contain either a "TRUE" or "FALSE" Boolean value. Format Definition: This value type is defined by the following notation: .. code-block:: text boolean = "TRUE" / "FALSE" Description: These values are case-insensitive text. No additional content value encoding is defined for this value type. Example: The following is an example of a hypothetical property that has a BOOLEAN value type: .. code-block:: python TRUE .. code-block:: pycon >>> from icalendar.prop import vBoolean >>> boolean = vBoolean.from_ical('TRUE') >>> boolean True >>> boolean = vBoolean.from_ical('FALSE') >>> boolean False >>> boolean = vBoolean.from_ical('True') >>> boolean True """ params: Parameters BOOL_MAP = CaselessDict({"true": True, "false": False}) def __new__(cls, *args, params={}, **kwargs): self = super().__new__(cls, *args, **kwargs) self.params = Parameters(params) return self def to_ical(self): return b"TRUE" if self else b"FALSE" @classmethod def from_ical(cls, ical): try: return cls.BOOL_MAP[ical] except Exception: raise ValueError(f"Expected 'TRUE' or 'FALSE'. Got {ical}") class vText(str): """Simple text.""" params: Parameters def __new__(cls, value, encoding=DEFAULT_ENCODING, params={}): value = to_unicode(value, encoding=encoding) self = super().__new__(cls, value) self.encoding = encoding self.params = Parameters(params) return self def __repr__(self) -> str: return f"vText({self.to_ical()!r})" def to_ical(self) -> bytes: return escape_char(self).encode(self.encoding) @classmethod def from_ical(cls, ical: ICAL_TYPE): ical_unesc = unescape_char(ical) return cls(ical_unesc) from icalendar.param import ALTREP, LANGUAGE, RELTYPE class vCalAddress(str): """Calendar User Address Value Name: CAL-ADDRESS Purpose: This value type is used to identify properties that contain a calendar user address. Description: The value is a URI as defined by [RFC3986] or any other IANA-registered form for a URI. When used to address an Internet email transport address for a calendar user, the value MUST be a mailto URI, as defined by [RFC2368]. Example: ``mailto:`` is in front of the address. .. code-block:: text mailto:jane_doe@example.com Parsing: .. code-block:: pycon >>> from icalendar import vCalAddress >>> cal_address = vCalAddress.from_ical('mailto:jane_doe@example.com') >>> cal_address vCalAddress('mailto:jane_doe@example.com') Encoding: .. code-block:: pycon >>> from icalendar import vCalAddress, Event >>> event = Event() >>> jane = vCalAddress("mailto:jane_doe@example.com") >>> jane.name = "Jane" >>> event["organizer"] = jane >>> print(event.to_ical()) BEGIN:VEVENT ORGANIZER;CN=Jane:mailto:jane_doe@example.com END:VEVENT """ params: Parameters def __new__(cls, value, encoding=DEFAULT_ENCODING, params={}): value = to_unicode(value, encoding=encoding) self = super().__new__(cls, value) self.params = Parameters(params) return self def __repr__(self): return f"vCalAddress('{self}')" def to_ical(self): return self.encode(DEFAULT_ENCODING) @classmethod def from_ical(cls, ical): return cls(ical) @property def email(self) -> str: """The email address without mailto: at the start.""" if self.lower().startswith("mailto:"): return self[7:] return str(self) from icalendar.param import ( CN, CUTYPE, DELEGATED_FROM, DELEGATED_TO, DIR, LANGUAGE, PARTSTAT, ROLE, RSVP, SENT_BY, ) name = CN class vFloat(float): """Float Value Name: FLOAT Purpose: This value type is used to identify properties that contain a real-number value. Format Definition: This value type is defined by the following notation: .. code-block:: text float = (["+"] / "-") 1*DIGIT ["." 1*DIGIT] Description: If the property permits, multiple "float" values are specified by a COMMA-separated list of values. Example: .. code-block:: text 1000000.0000001 1.333 -3.14 .. code-block:: pycon >>> from icalendar.prop import vFloat >>> float = vFloat.from_ical('1000000.0000001') >>> float 1000000.0000001 >>> float = vFloat.from_ical('1.333') >>> float 1.333 >>> float = vFloat.from_ical('+1.333') >>> float 1.333 >>> float = vFloat.from_ical('-3.14') >>> float -3.14 """ params: Parameters def __new__(cls, *args, params={}, **kwargs): self = super().__new__(cls, *args, **kwargs) self.params = Parameters(params) return self def to_ical(self): return str(self).encode("utf-8") @classmethod def from_ical(cls, ical): try: return cls(ical) except Exception: raise ValueError(f"Expected float value, got: {ical}") class vInt(int): """Integer Value Name: INTEGER Purpose: This value type is used to identify properties that contain a signed integer value. Format Definition: This value type is defined by the following notation: .. code-block:: text integer = (["+"] / "-") 1*DIGIT Description: If the property permits, multiple "integer" values are specified by a COMMA-separated list of values. The valid range for "integer" is -2147483648 to 2147483647. If the sign is not specified, then the value is assumed to be positive. Example: .. code-block:: text 1234567890 -1234567890 +1234567890 432109876 .. code-block:: pycon >>> from icalendar.prop import vInt >>> integer = vInt.from_ical('1234567890') >>> integer 1234567890 >>> integer = vInt.from_ical('-1234567890') >>> integer -1234567890 >>> integer = vInt.from_ical('+1234567890') >>> integer 1234567890 >>> integer = vInt.from_ical('432109876') >>> integer 432109876 """ params: Parameters def __new__(cls, *args, params={}, **kwargs): self = super().__new__(cls, *args, **kwargs) self.params = Parameters(params) return self def to_ical(self) -> bytes: return str(self).encode("utf-8") @classmethod def from_ical(cls, ical: ICAL_TYPE): try: return cls(ical) except Exception: raise ValueError(f"Expected int, got: {ical}") class vDDDLists: """A list of vDDDTypes values.""" params: Parameters dts: list 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"] params = {} if tzid: # NOTE: no support for multiple timezones here! params["TZID"] = tzid self.params = Parameters(params) self.dts = vDDD def to_ical(self): dts_ical = (from_unicode(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 def __eq__(self, other): if isinstance(other, vDDDLists): return self.dts == other.dts if isinstance(other, (TimeBase, date)): return self.dts == [other] return False def __repr__(self): """String representation.""" return f"{self.__class__.__name__}({self.dts})" class vCategory: params: Parameters def __init__(self, c_list: list[str] | str, params={}): if not hasattr(c_list, "__iter__") or isinstance(c_list, str): c_list = [c_list] self.cats: list[vText | str] = [vText(c) for c in c_list] self.params = Parameters(params) def __iter__(self): return iter(vCategory.from_ical(self.to_ical())) def to_ical(self): return b",".join( [ c.to_ical() if hasattr(c, "to_ical") else vText(c).to_ical() for c in self.cats ] ) @staticmethod def from_ical(ical): ical = to_unicode(ical) out = unescape_char(ical).split(",") return out def __eq__(self, other): """self == other""" return isinstance(other, vCategory) and self.cats == other.cats class TimeBase: """Make classes with a datetime/date comparable.""" params: Parameters ignore_for_equality = {"TZID", "VALUE"} def __eq__(self, other): """self == other""" if isinstance(other, date): return self.dt == other if isinstance(other, TimeBase): default = object() for key in ( set(self.params) | set(other.params) ) - self.ignore_for_equality: if key[:2].lower() != "x-" and self.params.get( key, default ) != other.params.get(key, default): return False return self.dt == other.dt if isinstance(other, vDDDLists): return other == self return False def __hash__(self): return hash(self.dt) from icalendar.param import RANGE, RELATED, TZID def __repr__(self): """String representation.""" return f"{self.__class__.__name__}({self.dt}, {self.params})" class vDDDTypes(TimeBase): """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. """ params: Parameters def __init__(self, dt): if not isinstance(dt, (datetime, date, timedelta, time, tuple)): raise ValueError( "You must use datetime, date, timedelta, " "time or tuple (for periods)" ) if isinstance(dt, (datetime, timedelta)): self.params = Parameters() elif isinstance(dt, date): self.params = Parameters({"value": "DATE"}) elif isinstance(dt, time): self.params = Parameters({"value": "TIME"}) else: # isinstance(dt, tuple) self.params = Parameters({"value": "PERIOD"}) tzid = tzid_from_dt(dt) if isinstance(dt, (datetime, time)) else None if tzid is not None and tzid != "UTC": 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() elif isinstance(dt, tuple) and len(dt) == 2: return vPeriod(dt).to_ical() else: raise ValueError(f"Unknown date type: {type(dt)}") @classmethod def from_ical(cls, ical, timezone=None): if isinstance(ical, cls): return ical.dt u = ical.upper() if u.startswith(("P", "-P", "+P")): return vDuration.from_ical(ical) if "/" in u: return vPeriod.from_ical(ical, timezone=timezone) if len(ical) in (15, 16): return vDatetime.from_ical(ical, timezone=timezone) elif len(ical) == 8: return vDate.from_ical(ical) elif len(ical) in (6, 7): return vTime.from_ical(ical) else: raise ValueError(f"Expected datetime, date, or time, got: '{ical}'") class vDate(TimeBase): """Date Value Name: DATE Purpose: This value type is used to identify values that contain a calendar date. Format Definition: This value type is defined by the following notation: .. code-block:: text date = date-value date-value = date-fullyear date-month date-mday date-fullyear = 4DIGIT date-month = 2DIGIT ;01-12 date-mday = 2DIGIT ;01-28, 01-29, 01-30, 01-31 ;based on month/year Description: If the property permits, multiple "date" values are specified as a COMMA-separated list of values. The format for the value type is based on the [ISO.8601.2004] complete representation, basic format for a calendar date. The textual format specifies a four-digit year, two-digit month, and two-digit day of the month. There are no separator characters between the year, month, and day component text. Example: The following represents July 14, 1997: .. code-block:: text 19970714 .. code-block:: pycon >>> from icalendar.prop import vDate >>> date = vDate.from_ical('19970714') >>> date.year 1997 >>> date.month 7 >>> date.day 14 """ params: Parameters def __init__(self, dt): if not isinstance(dt, date): raise ValueError("Value MUST be a date instance") self.dt = dt self.params = Parameters({"value": "DATE"}) def to_ical(self): s = f"{self.dt.year:04}{self.dt.month:02}{self.dt.day:02}" 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 Exception: raise ValueError(f"Wrong date format {ical}") class vDatetime(TimeBase): """Render and generates icalendar datetime format. vDatetime is timezone aware and uses a timezone library. When a vDatetime object is created from an ical string, you can pass a valid timezone identifier. When a vDatetime object is created from a python datetime object, it uses the tzinfo component, if present. Otherwise a timezone-naive object is created. Be aware that there are certain limitations with timezone naive DATE-TIME components in the icalendar standard. """ params: Parameters def __init__(self, dt, params={}): self.dt = dt self.params = Parameters(params) def to_ical(self): dt = self.dt tzid = tzid_from_dt(dt) s = f"{dt.year:04}{dt.month:02}{dt.day:02}T{dt.hour:02}{dt.minute:02}{dt.second:02}" if tzid == "UTC": s += "Z" elif tzid: self.params.update({"TZID": tzid}) return s.encode("utf-8") @staticmethod def from_ical(ical, timezone=None): """Create a datetime from the RFC string. Format: .. code-block:: text YYYYMMDDTHHMMSS .. code-block:: pycon >>> from icalendar import vDatetime >>> vDatetime.from_ical("20210302T101500") datetime.datetime(2021, 3, 2, 10, 15) >>> vDatetime.from_ical("20210302T101500", "America/New_York") datetime.datetime(2021, 3, 2, 10, 15, tzinfo=ZoneInfo(key='America/New_York')) >>> from zoneinfo import ZoneInfo >>> timezone = ZoneInfo("Europe/Berlin") >>> vDatetime.from_ical("20210302T101500", timezone) datetime.datetime(2021, 3, 2, 10, 15, tzinfo=ZoneInfo(key='Europe/Berlin')) """ tzinfo = None if isinstance(timezone, str): tzinfo = tzp.timezone(timezone) elif timezone is not None: tzinfo = timezone 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 tzp.localize(datetime(*timetuple), tzinfo) elif not ical[15:]: return datetime(*timetuple) elif ical[15:16] == "Z": return tzp.localize_utc(datetime(*timetuple)) else: raise ValueError(ical) except Exception as e: raise ValueError(f"Wrong datetime format: {ical}") from e class vDuration(TimeBase): """Duration Value Name: DURATION Purpose: This value type is used to identify properties that contain a duration of time. Format Definition: This value type is defined by the following notation: .. code-block:: text dur-value = (["+"] / "-") "P" (dur-date / dur-time / dur-week) dur-date = dur-day [dur-time] dur-time = "T" (dur-hour / dur-minute / dur-second) dur-week = 1*DIGIT "W" dur-hour = 1*DIGIT "H" [dur-minute] dur-minute = 1*DIGIT "M" [dur-second] dur-second = 1*DIGIT "S" dur-day = 1*DIGIT "D" Description: If the property permits, multiple "duration" values are specified by a COMMA-separated list of values. The format is based on the [ISO.8601.2004] complete representation basic format with designators for the duration of time. The format can represent nominal durations (weeks and days) and accurate durations (hours, minutes, and seconds). Note that unlike [ISO.8601.2004], this value type doesn't support the "Y" and "M" designators to specify durations in terms of years and months. The duration of a week or a day depends on its position in the calendar. In the case of discontinuities in the time scale, such as the change from standard time to daylight time and back, the computation of the exact duration requires the subtraction or addition of the change of duration of the discontinuity. Leap seconds MUST NOT be considered when computing an exact duration. When computing an exact duration, the greatest order time components MUST be added first, that is, the number of days MUST be added first, followed by the number of hours, number of minutes, and number of seconds. Example: A duration of 15 days, 5 hours, and 20 seconds would be: .. code-block:: text P15DT5H0M20S A duration of 7 weeks would be: .. code-block:: text P7W .. code-block:: pycon >>> from icalendar.prop import vDuration >>> duration = vDuration.from_ical('P15DT5H0M20S') >>> duration datetime.timedelta(days=15, seconds=18020) >>> duration = vDuration.from_ical('P7W') >>> duration datetime.timedelta(days=49) """ params: Parameters def __init__(self, td, params={}): if not isinstance(td, timedelta): raise ValueError("Value MUST be a timedelta instance") self.td = td self.params = Parameters(params) def to_ical(self): sign = "" td = self.td if td.days < 0: sign = "-" td = -td timepart = "" if td.seconds: timepart = "T" hours = td.seconds // 3600 minutes = td.seconds % 3600 // 60 seconds = td.seconds % 60 if hours: timepart += f"{hours}H" if minutes or (hours and seconds): timepart += f"{minutes}M" if seconds: timepart += f"{seconds}S" if td.days == 0 and timepart: return str(sign).encode("utf-8") + b"P" + str(timepart).encode("utf-8") else: return ( str(sign).encode("utf-8") + b"P" + str(abs(td.days)).encode("utf-8") + b"D" + str(timepart).encode("utf-8") ) @staticmethod def from_ical(ical): match = DURATION_REGEX.match(ical) if not match: raise ValueError(f"Invalid iCalendar duration: {ical}") sign, weeks, days, hours, minutes, seconds = match.groups() value = timedelta( weeks=int(weeks or 0), 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 @property def dt(self) -> timedelta: """The time delta for compatibility.""" return self.td class vPeriod(TimeBase): """Period of Time Value Name: PERIOD Purpose: This value type is used to identify values that contain a precise period of time. Format Definition: This value type is defined by the following notation: .. code-block:: text period = period-explicit / period-start period-explicit = date-time "/" date-time ; [ISO.8601.2004] complete representation basic format for a ; period of time consisting of a start and end. The start MUST ; be before the end. period-start = date-time "/" dur-value ; [ISO.8601.2004] complete representation basic format for a ; period of time consisting of a start and positive duration ; of time. Description: If the property permits, multiple "period" values are specified by a COMMA-separated list of values. There are two forms of a period of time. First, a period of time is identified by its start and its end. This format is based on the [ISO.8601.2004] complete representation, basic format for "DATE- TIME" start of the period, followed by a SOLIDUS character followed by the "DATE-TIME" of the end of the period. The start of the period MUST be before the end of the period. Second, a period of time can also be defined by a start and a positive duration of time. The format is based on the [ISO.8601.2004] complete representation, basic format for the "DATE-TIME" start of the period, followed by a SOLIDUS character, followed by the [ISO.8601.2004] basic format for "DURATION" of the period. Example: The period starting at 18:00:00 UTC, on January 1, 1997 and ending at 07:00:00 UTC on January 2, 1997 would be: .. code-block:: text 19970101T180000Z/19970102T070000Z The period start at 18:00:00 on January 1, 1997 and lasting 5 hours and 30 minutes would be: .. code-block:: text 19970101T180000Z/PT5H30M .. code-block:: pycon >>> from icalendar.prop import vPeriod >>> period = vPeriod.from_ical('19970101T180000Z/19970102T070000Z') >>> period = vPeriod.from_ical('19970101T180000Z/PT5H30M') """ params: Parameters def __init__(self, per: tuple[datetime, Union[datetime, timedelta]]): 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({"value": "PERIOD"}) # 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 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, timezone=None): try: start, end_or_duration = ical.split("/") start = vDDDTypes.from_ical(start, timezone=timezone) end_or_duration = vDDDTypes.from_ical(end_or_duration, timezone=timezone) return (start, end_or_duration) except Exception: raise ValueError(f"Expected period format, got: {ical}") def __repr__(self): if self.by_duration: p = (self.start, self.duration) else: p = (self.start, self.end) return f"vPeriod({p!r})" @property def dt(self): """Make this cooperate with the other vDDDTypes.""" return (self.start, (self.duration if self.by_duration else self.end)) from icalendar.param import FBTYPE class vWeekday(str): """Either a ``weekday`` or a ``weekdaynum`` .. code-block:: pycon >>> from icalendar import vWeekday >>> vWeekday("MO") # Simple weekday 'MO' >>> vWeekday("2FR").relative # Second friday 2 >>> vWeekday("2FR").weekday 'FR' >>> vWeekday("-1SU").relative # Last Sunday -1 Definition from `RFC 5545, Section 3.3.10 `_: .. code-block:: text weekdaynum = [[plus / minus] ordwk] weekday plus = "+" minus = "-" ordwk = 1*2DIGIT ;1 to 53 weekday = "SU" / "MO" / "TU" / "WE" / "TH" / "FR" / "SA" ;Corresponding to SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, ;FRIDAY, and SATURDAY days of the week. """ params: Parameters week_days = CaselessDict( { "SU": 0, "MO": 1, "TU": 2, "WE": 3, "TH": 4, "FR": 5, "SA": 6, } ) def __new__(cls, value, encoding=DEFAULT_ENCODING, params={}): value = to_unicode(value, encoding=encoding) self = super().__new__(cls, value) match = WEEKDAY_RULE.match(self) if match is None: raise ValueError(f"Expected weekday abbrevation, got: {self}") match = match.groupdict() sign = match["signal"] weekday = match["weekday"] relative = match["relative"] if weekday not in vWeekday.week_days or sign not in "+-": raise ValueError(f"Expected weekday abbrevation, got: {self}") self.weekday = weekday or None self.relative = relative and int(relative) or None if sign == "-" and self.relative: self.relative *= -1 self.params = Parameters(params) return self def to_ical(self): return self.encode(DEFAULT_ENCODING).upper() @classmethod def from_ical(cls, ical): try: return cls(ical.upper()) except Exception: raise ValueError(f"Expected weekday abbrevation, got: {ical}") class vFrequency(str): """A simple class that catches illegal values.""" params: Parameters frequencies = CaselessDict( { "SECONDLY": "SECONDLY", "MINUTELY": "MINUTELY", "HOURLY": "HOURLY", "DAILY": "DAILY", "WEEKLY": "WEEKLY", "MONTHLY": "MONTHLY", "YEARLY": "YEARLY", } ) def __new__(cls, value, encoding=DEFAULT_ENCODING, params={}): value = to_unicode(value, encoding=encoding) self = super().__new__(cls, value) if self not in vFrequency.frequencies: raise ValueError(f"Expected frequency, got: {self}") self.params = Parameters(params) return self def to_ical(self): return self.encode(DEFAULT_ENCODING).upper() @classmethod def from_ical(cls, ical): try: return cls(ical.upper()) except Exception: raise ValueError(f"Expected frequency, got: {ical}") class vMonth(int): """The number of the month for recurrence. In :rfc:`5545`, this is just an int. In :rfc:`7529`, this can be followed by `L` to indicate a leap month. .. code-block:: pycon >>> from icalendar import vMonth >>> vMonth(1) # first month January vMonth('1') >>> vMonth("5L") # leap month in Hebrew calendar vMonth('5L') >>> vMonth(1).leap False >>> vMonth("5L").leap True Definition from RFC: .. code-block:: text type-bymonth = element bymonth { xsd:positiveInteger | xsd:string } """ params: Parameters def __new__(cls, month: Union[str, int], params={}): if isinstance(month, vMonth): return cls(month.to_ical().decode()) if isinstance(month, str): if month.isdigit(): month_index = int(month) leap = False else: if month[-1] != "L" and month[:-1].isdigit(): raise ValueError(f"Invalid month: {month!r}") month_index = int(month[:-1]) leap = True else: leap = False month_index = int(month) self = super().__new__(cls, month_index) self.leap = leap self.params = Parameters(params) return self def to_ical(self) -> bytes: """The ical representation.""" return str(self).encode("utf-8") @classmethod def from_ical(cls, ical: str): return cls(ical) def leap(): doc = "Whether this is a leap month." def fget(self) -> bool: return self._leap def fset(self, value: bool) -> None: self._leap = value return locals() leap = property(**leap()) def __repr__(self) -> str: """repr(self)""" return f"{self.__class__.__name__}({str(self)!r})" def __str__(self) -> str: """str(self)""" return f"{int(self)}{'L' if self.leap else ''}" class vSkip(vText, Enum): """Skip values for RRULE. These are defined in :rfc:`7529`. OMIT is the default value. Examples: .. code-block:: pycon >>> from icalendar import vSkip >>> vSkip.OMIT vSkip('OMIT') >>> vSkip.FORWARD vSkip('FORWARD') >>> vSkip.BACKWARD vSkip('BACKWARD') """ OMIT = "OMIT" FORWARD = "FORWARD" BACKWARD = "BACKWARD" __reduce_ex__ = Enum.__reduce_ex__ def __repr__(self): return f"{self.__class__.__name__}({self._name_!r})" class vRecur(CaselessDict): """Recurrence definition. Property Name: RRULE Purpose: This property defines a rule or repeating pattern for recurring events, to-dos, journal entries, or time zone definitions. Value Type: RECUR Property Parameters: IANA and non-standard property parameters can be specified on this property. Conformance: This property can be specified in recurring "VEVENT", "VTODO", and "VJOURNAL" calendar components as well as in the "STANDARD" and "DAYLIGHT" sub-components of the "VTIMEZONE" calendar component, but it SHOULD NOT be specified more than once. The recurrence set generated with multiple "RRULE" properties is undefined. Description: The recurrence rule, if specified, is used in computing the recurrence set. The recurrence set is the complete set of recurrence instances for a calendar component. The recurrence set is generated by considering the initial "DTSTART" property along with the "RRULE", "RDATE", and "EXDATE" properties contained within the recurring component. The "DTSTART" property defines the first instance in the recurrence set. The "DTSTART" property value SHOULD be synchronized with the recurrence rule, if specified. The recurrence set generated with a "DTSTART" property value not synchronized with the recurrence rule is undefined. The final recurrence set is generated by gathering all of the start DATE-TIME values generated by any of the specified "RRULE" and "RDATE" properties, and then excluding any start DATE-TIME values specified by "EXDATE" properties. This implies that start DATE- TIME values specified by "EXDATE" properties take precedence over those specified by inclusion properties (i.e., "RDATE" and "RRULE"). Where duplicate instances are generated by the "RRULE" and "RDATE" properties, only one recurrence is considered. Duplicate instances are ignored. The "DTSTART" property specified within the iCalendar object defines the first instance of the recurrence. In most cases, a "DTSTART" property of DATE-TIME value type used with a recurrence rule, should be specified as a date with local time and time zone reference to make sure all the recurrence instances start at the same local time regardless of time zone changes. If the duration of the recurring component is specified with the "DTEND" or "DUE" property, then the same exact duration will apply to all the members of the generated recurrence set. Else, if the duration of the recurring component is specified with the "DURATION" property, then the same nominal duration will apply to all the members of the generated recurrence set and the exact duration of each recurrence instance will depend on its specific start time. For example, recurrence instances of a nominal duration of one day will have an exact duration of more or less than 24 hours on a day where a time zone shift occurs. The duration of a specific recurrence may be modified in an exception component or simply by using an "RDATE" property of PERIOD value type. Examples: The following RRULE specifies daily events for 10 occurrences. .. code-block:: text RRULE:FREQ=DAILY;COUNT=10 Below, we parse the RRULE ical string. .. code-block:: pycon >>> from icalendar.prop import vRecur >>> rrule = vRecur.from_ical('FREQ=DAILY;COUNT=10') >>> rrule vRecur({'FREQ': ['DAILY'], 'COUNT': [10]}) You can choose to add an rrule to an :class:`icalendar.cal.Event` or :class:`icalendar.cal.Todo`. .. code-block:: pycon >>> from icalendar import Event >>> event = Event() >>> event.add('RRULE', 'FREQ=DAILY;COUNT=10') >>> event.rrules [vRecur({'FREQ': ['DAILY'], 'COUNT': [10]})] """ params: Parameters 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 = ( "RSCALE", "FREQ", "UNTIL", "COUNT", "INTERVAL", "BYSECOND", "BYMINUTE", "BYHOUR", "BYDAY", "BYWEEKDAY", "BYMONTHDAY", "BYYEARDAY", "BYWEEKNO", "BYMONTH", "BYSETPOS", "WKST", "SKIP", ) types = CaselessDict( { "COUNT": vInt, "INTERVAL": vInt, "BYSECOND": vInt, "BYMINUTE": vInt, "BYHOUR": vInt, "BYWEEKNO": vInt, "BYMONTHDAY": vInt, "BYYEARDAY": vInt, "BYMONTH": vMonth, "UNTIL": vDDDTypes, "BYSETPOS": vInt, "WKST": vWeekday, "BYDAY": vWeekday, "FREQ": vFrequency, "BYWEEKDAY": vWeekday, "SKIP": vSkip, } ) def __init__(self, *args, params={}, **kwargs): if args and isinstance(args[0], str): # we have a string as an argument. args = (self.from_ical(args[0]),) + args[1:] for k, v in kwargs.items(): if not isinstance(v, SEQUENCE_TYPES): kwargs[k] = [v] super().__init__(*args, **kwargs) self.params = Parameters(params) def to_ical(self): result = [] for key, vals in self.sorted_items(): typ = self.types.get(key, vText) 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: str): if isinstance(ical, cls): return ical try: recur = cls() for pairs in ical.split(";"): try: key, vals = pairs.split("=") except ValueError: # E.g. incorrect trailing semicolon, like (issue #157): # FREQ=YEARLY;BYMONTH=11;BYDAY=1SU; continue recur[key] = cls.parse_type(key, vals) return cls(recur) except ValueError: raise except: raise ValueError(f"Error in recurrence rule: {ical}") class vTime(TimeBase): """Time Value Name: TIME Purpose: This value type is used to identify values that contain a time of day. Format Definition: This value type is defined by the following notation: .. code-block:: text time = time-hour time-minute time-second [time-utc] time-hour = 2DIGIT ;00-23 time-minute = 2DIGIT ;00-59 time-second = 2DIGIT ;00-60 ;The "60" value is used to account for positive "leap" seconds. time-utc = "Z" Description: If the property permits, multiple "time" values are specified by a COMMA-separated list of values. No additional content value encoding (i.e., BACKSLASH character encoding, see vText) is defined for this value type. The "TIME" value type is used to identify values that contain a time of day. The format is based on the [ISO.8601.2004] complete representation, basic format for a time of day. The text format consists of a two-digit, 24-hour of the day (i.e., values 00-23), two-digit minute in the hour (i.e., values 00-59), and two-digit seconds in the minute (i.e., values 00-60). The seconds value of 60 MUST only be used to account for positive "leap" seconds. Fractions of a second are not supported by this format. In parallel to the "DATE-TIME" definition above, the "TIME" value type expresses time values in three forms: The form of time with UTC offset MUST NOT be used. For example, the following is not valid for a time value: .. code-block:: text 230000-0800 ;Invalid time format **FORM #1 LOCAL TIME** The local time form is simply a time value that does not contain the UTC designator nor does it reference a time zone. For example, 11:00 PM: .. code-block:: text 230000 Time values of this type are said to be "floating" and are not bound to any time zone in particular. They are used to represent the same hour, minute, and second value regardless of which time zone is currently being observed. For example, an event can be defined that indicates that an individual will be busy from 11:00 AM to 1:00 PM every day, no matter which time zone the person is in. In these cases, a local time can be specified. The recipient of an iCalendar object with a property value consisting of a local time, without any relative time zone information, SHOULD interpret the value as being fixed to whatever time zone the "ATTENDEE" is in at any given moment. This means that two "Attendees", may participate in the same event at different UTC times; floating time SHOULD only be used where that is reasonable behavior. In most cases, a fixed time is desired. To properly communicate a fixed time in a property value, either UTC time or local time with time zone reference MUST be specified. The use of local time in a TIME value without the "TZID" property parameter is to be interpreted as floating time, regardless of the existence of "VTIMEZONE" calendar components in the iCalendar object. **FORM #2: UTC TIME** UTC time, or absolute time, is identified by a LATIN CAPITAL LETTER Z suffix character, the UTC designator, appended to the time value. For example, the following represents 07:00 AM UTC: .. code-block:: text 070000Z The "TZID" property parameter MUST NOT be applied to TIME properties whose time values are specified in UTC. **FORM #3: LOCAL TIME AND TIME ZONE REFERENCE** The local time with reference to time zone information form is identified by the use the "TZID" property parameter to reference the appropriate time zone definition. Example: The following represents 8:30 AM in New York in winter, five hours behind UTC, in each of the three formats: .. code-block:: text 083000 133000Z TZID=America/New_York:083000 """ def __init__(self, *args): if len(args) == 1: if not isinstance(args[0], (time, datetime)): raise ValueError(f"Expected a datetime.time, got: {args[0]}") self.dt = args[0] else: self.dt = time(*args) self.params = Parameters({"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 Exception: raise ValueError(f"Expected time, got: {ical}") class vUri(str): """URI Value Name: URI Purpose: This value type is used to identify values that contain a uniform resource identifier (URI) type of reference to the property value. Format Definition: This value type is defined by the following notation: .. code-block:: text uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ] Description: This value type might be used to reference binary information, for values that are large, or otherwise undesirable to include directly in the iCalendar object. Property values with this value type MUST follow the generic URI syntax defined in [RFC3986]. When a property parameter value is a URI value type, the URI MUST be specified as a quoted-string value. Example: The following is a URI for a network file: .. code-block:: text http://example.com/my-report.txt .. code-block:: pycon >>> from icalendar.prop import vUri >>> uri = vUri.from_ical('http://example.com/my-report.txt') >>> uri 'http://example.com/my-report.txt' """ params: Parameters def __new__(cls, value, encoding=DEFAULT_ENCODING, params={}): value = to_unicode(value, encoding=encoding) self = super().__new__(cls, value) self.params = Parameters(params) return self def to_ical(self): return self.encode(DEFAULT_ENCODING) @classmethod def from_ical(cls, ical): try: return cls(ical) except Exception: raise ValueError(f"Expected , got: {ical}") class vGeo: """Geographic Position Property Name: GEO Purpose: This property specifies information related to the global position for the activity specified by a calendar component. Value Type: FLOAT. The value MUST be two SEMICOLON-separated FLOAT values. Property Parameters: IANA and non-standard property parameters can be specified on this property. Conformance: This property can be specified in "VEVENT" or "VTODO" calendar components. Description: This property value specifies latitude and longitude, in that order (i.e., "LAT LON" ordering). The longitude represents the location east or west of the prime meridian as a positive or negative real number, respectively. The longitude and latitude values MAY be specified up to six decimal places, which will allow for accuracy to within one meter of geographical position. Receiving applications MUST accept values of this precision and MAY truncate values of greater precision. Example: .. code-block:: text GEO:37.386013;-122.082932 Parse vGeo: .. code-block:: pycon >>> from icalendar.prop import vGeo >>> geo = vGeo.from_ical('37.386013;-122.082932') >>> geo (37.386013, -122.082932) Add a geo location to an event: .. code-block:: pycon >>> from icalendar import Event >>> event = Event() >>> latitude = 37.386013 >>> longitude = -122.082932 >>> event.add('GEO', (latitude, longitude)) >>> event['GEO'] vGeo((37.386013, -122.082932)) """ params: Parameters def __init__(self, geo: tuple[float | str | int, float | str | int], params={}): """Create a new vGeo from a tuple of (latitude, longitude). Raises: ValueError: if geo is not a tuple of (latitude, longitude) """ try: latitude, longitude = (geo[0], geo[1]) latitude = float(latitude) longitude = float(longitude) except Exception as e: raise ValueError( "Input must be (float, float) for " "latitude and longitude" ) from e self.latitude = latitude self.longitude = longitude self.params = Parameters(params) def to_ical(self): return f"{self.latitude};{self.longitude}" @staticmethod def from_ical(ical): try: latitude, longitude = ical.split(";") return (float(latitude), float(longitude)) except Exception as e: raise ValueError(f"Expected 'float;float' , got: {ical}") from e def __eq__(self, other): return self.to_ical() == other.to_ical() def __repr__(self): """repr(self)""" return f"{self.__class__.__name__}(({self.latitude}, {self.longitude}))" class vUTCOffset: """UTC Offset Value Name: UTC-OFFSET Purpose: This value type is used to identify properties that contain an offset from UTC to local time. Format Definition: This value type is defined by the following notation: .. code-block:: text utc-offset = time-numzone time-numzone = ("+" / "-") time-hour time-minute [time-second] Description: The PLUS SIGN character MUST be specified for positive UTC offsets (i.e., ahead of UTC). The HYPHEN-MINUS character MUST be specified for negative UTC offsets (i.e., behind of UTC). The value of "-0000" and "-000000" are not allowed. The time-second, if present, MUST NOT be 60; if absent, it defaults to zero. Example: The following UTC offsets are given for standard time for New York (five hours behind UTC) and Geneva (one hour ahead of UTC): .. code-block:: text -0500 +0100 .. code-block:: pycon >>> from icalendar.prop import vUTCOffset >>> utc_offset = vUTCOffset.from_ical('-0500') >>> utc_offset datetime.timedelta(days=-1, seconds=68400) >>> utc_offset = vUTCOffset.from_ical('+0100') >>> utc_offset datetime.timedelta(seconds=3600) """ params: Parameters ignore_exceptions = False # if True, and we cannot parse this # component, we will silently ignore # it, rather than let the exception # propagate upwards def __init__(self, td, params={}): if not isinstance(td, timedelta): raise ValueError("Offset value MUST be a timedelta instance") self.td = td self.params = Parameters(params) 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 = f"{hours:02}{minutes:02}{seconds:02}" else: duration = f"{hours:02}{minutes:02}" 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 Exception: raise ValueError(f"Expected utc offset, got: {ical}") if not cls.ignore_exceptions and offset >= timedelta(hours=24): raise ValueError(f"Offset must be less than 24 hours, was {ical}") if sign == "-": return -offset return offset def __eq__(self, other): if not isinstance(other, vUTCOffset): return False return self.td == other.td def __hash__(self): return hash(self.td) def __repr__(self): return f"vUTCOffset({self.td!r})" class vInline(str): """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. """ params: Parameters def __new__(cls, value, encoding=DEFAULT_ENCODING, params={}): value = to_unicode(value, encoding=encoding) self = super().__new__(cls, value) self.params = Parameters(params) 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 5545 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" super().__init__(*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, vCategory, ) 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 self["categories"] = vCategory ################################################# # 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": "categories", "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", "acknowledged": "date-time", # 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", "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 __all__ = [ "DURATION_REGEX", "TimeBase", "TypesFactory", "WEEKDAY_RULE", "tzid_from_dt", "vBinary", "vBoolean", "vCalAddress", "vCategory", "vDDDLists", "vDDDTypes", "vDate", "vDatetime", "vDuration", "vFloat", "vFrequency", "vGeo", "vInline", "vInt", "vMonth", "vPeriod", "vRecur", "vText", "vTime", "vUTCOffset", "vUri", "vWeekday", "tzid_from_tzinfo", "vSkip", ] icalendar-6.3.1/src/icalendar/tests/000077500000000000000000000000001501302773300173175ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/__init__.py000066400000000000000000000000001501302773300214160ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/alarms/000077500000000000000000000000001501302773300205765ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/alarms/rfc_5545_absolute_alarm_example.ics000066400000000000000000000002521501302773300273160ustar00rootroot00000000000000BEGIN:VALARM TRIGGER;VALUE=DATE-TIME:19970317T133000Z REPEAT:4 DURATION:PT15M ACTION:AUDIO ATTACH;FMTTYPE=audio/basic:ftp://example.com/pub/sounds/bell-01.aud END:VALARM icalendar-6.3.1/src/icalendar/tests/alarms/rfc_5545_end.ics000066400000000000000000000006311501302773300233600ustar00rootroot00000000000000BEGIN:VALARM TRIGGER;RELATED=END:-P2D ACTION:EMAIL ATTENDEE:mailto:john_doe@example.com SUMMARY:*** REMINDER: SEND AGENDA FOR WEEKLY STAFF MEETING *** DESCRIPTION:A draft agenda needs to be sent out to the attendees to the weekly managers meeting (MGR-LIST). Attached is a pointer the document template for the agenda file. ATTACH;FMTTYPE=application/msword:http://example.com/templates/agenda.doc END:VALARM icalendar-6.3.1/src/icalendar/tests/alarms/start_date.ics000066400000000000000000000006331501302773300234320ustar00rootroot00000000000000BEGIN:VALARM TRIGGER;RELATED=START:-P2D ACTION:EMAIL ATTENDEE:mailto:john_doe@example.com SUMMARY:*** REMINDER: SEND AGENDA FOR WEEKLY STAFF MEETING *** DESCRIPTION:A draft agenda needs to be sent out to the attendees to the weekly managers meeting (MGR-LIST). Attached is a pointer the document template for the agenda file. ATTACH;FMTTYPE=application/msword:http://example.com/templates/agenda.doc END:VALARM icalendar-6.3.1/src/icalendar/tests/attr/000077500000000000000000000000001501302773300202715ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/attr/__init__.py000066400000000000000000000000551501302773300224020ustar00rootroot00000000000000"""Test the getters/setters of components."""icalendar-6.3.1/src/icalendar/tests/attr/test_alarm.py000066400000000000000000000021271501302773300230000ustar00rootroot00000000000000"""Test the properties of the alarm.""" import pytest from icalendar.cal import Alarm from icalendar.error import InvalidCalendar def test_repeat_absent(): """Test the absence of REPEAT.""" assert Alarm().REPEAT == 0 def test_repeat_number(): """Test the absence of REPEAT.""" assert Alarm({"REPEAT": 10}).REPEAT == 10 def test_set_REPEAT(): """Check setting the value.""" a = Alarm() a.REPEAT = 10 assert a.REPEAT == 10 def test_set_REPEAT_twice(): """Check setting the value.""" a = Alarm() a.REPEAT = 10 a.REPEAT = 20 assert a.REPEAT == 20 def test_add_REPEAT(): """Check setting the value.""" a = Alarm() a.add("REPEAT", 10) assert a.REPEAT == 10 def test_invalid_repeat_value(): """Check setting the value.""" a = Alarm() with pytest.raises(ValueError): a.REPEAT = "asd" a["REPEAT"] = "asd" with pytest.raises(InvalidCalendar): a.REPEAT # noqa: B018 def test_alarm_to_string(): a = Alarm() a.REPEAT = 11 assert a.to_ical() == b"BEGIN:VALARM\r\nREPEAT:11\r\nEND:VALARM\r\n" icalendar-6.3.1/src/icalendar/tests/attr/test_component.py000066400000000000000000000060051501302773300237050ustar00rootroot00000000000000"""Test common properties of components.""" from datetime import date, datetime, timedelta import pytest from icalendar import Event, FreeBusy, Journal, Todo, vDDDTypes from icalendar.cal import Component from icalendar.error import InvalidCalendar @pytest.fixture(params=[Event, Todo, Journal, FreeBusy]) def dtstamp_comp(request): """a component to test""" return request.param() def test_no_dtstamp(dtstamp_comp): """We have None as a value.""" assert dtstamp_comp.DTSTAMP is None def set_dtstamp_attribute(component: Component, value: date): """Use the setter.""" component.DTSTAMP = value def set_dtstamp_item(component: Component, value: date): """Use setitem.""" component["DTSTAMP"] = vDDDTypes(value) def set_dtstamp_add(component: Component, value: date): """Use add.""" component.add("DTSTAMP", value) @pytest.mark.parametrize( ("value", "timezone", "expected"), [ (datetime(2024, 10, 11, 23, 1), None, datetime(2024, 10, 11, 23, 1)), (datetime(2024, 10, 11, 23, 1), "Europe/Berlin", datetime(2024, 10, 11, 21, 1)), (datetime(2024, 10, 11, 22, 1), "UTC", datetime(2024, 10, 11, 22, 1)), (date(2024, 10, 10), None, datetime(2024, 10, 10)), ], ) @pytest.mark.parametrize( "set_dtstamp", [set_dtstamp_add, set_dtstamp_attribute, set_dtstamp_item] ) def test_set_value_and_get_it( dtstamp_comp, value, timezone, expected, tzp, set_dtstamp ): """Set and get the DTSTAMP value.""" dtstamp = value if timezone is None else tzp.localize(value, timezone) set_dtstamp(dtstamp_comp, dtstamp) in_utc = tzp.localize_utc(expected) get_value = dtstamp_comp.get("DTSTAMP").dt assert in_utc == get_value or set_dtstamp != set_dtstamp_attribute assert in_utc == dtstamp_comp.DTSTAMP @pytest.mark.parametrize("invalid_value", [None, timedelta()]) def test_set_invalid_value(invalid_value, dtstamp_comp): """Check handling of invalid values.""" with pytest.raises(TypeError) as e: dtstamp_comp.DTSTAMP = invalid_value assert e.value.args[0] == f"DTSTAMP takes a datetime in UTC, not {invalid_value}" @pytest.mark.parametrize("invalid_value", [None, vDDDTypes(timedelta())]) def test_get_invalid_value(invalid_value, dtstamp_comp): """Check handling of invalid values.""" dtstamp_comp["DTSTAMP"] = invalid_value with pytest.raises(InvalidCalendar) as e: dtstamp_comp.DTSTAMP # noqa: B018 assert ( e.value.args[0] == f"DTSTAMP must be a datetime in UTC, not {getattr(invalid_value, 'dt', invalid_value)}" ) def test_set_twice(dtstamp_comp, tzp): """Set the value twice.""" dtstamp_comp.DTSTAMP = date(2014, 1, 1) dtstamp_comp.DTSTAMP = date(2014, 1, 2) assert tzp.localize_utc(datetime(2014, 1, 2)) == dtstamp_comp.DTSTAMP def test_last_modified(dtstamp_comp, tzp): """Check we can set LAST_MODIFIED in the same way as DTSTAMP""" dtstamp_comp.LAST_MODIFIED = date(2014, 1, 2) assert tzp.localize_utc(datetime(2014, 1, 2)) == dtstamp_comp.LAST_MODIFIED icalendar-6.3.1/src/icalendar/tests/attr/test_exdates.py000066400000000000000000000033061501302773300233410ustar00rootroot00000000000000"""This tests the exdate property. """ from __future__ import annotations from datetime import date, datetime from pprint import pprint from typing import Union import pytest from icalendar import ( Calendar, Event, Journal, TimezoneDaylight, TimezoneStandard, Todo, ) C_EXDATE = Union[Event, Todo, Journal, TimezoneDaylight, TimezoneStandard] @pytest.fixture(params = [Event, Todo, Journal, TimezoneDaylight, TimezoneStandard]) def c_exdate(request) -> C_EXDATE: """Return a component that uses exdate.""" return request.param() @pytest.fixture( params=[ lambda _tzp: date(2019, 10, 11), lambda _tzp: datetime(2000, 1, 13, 12, 1), lambda tzp: tzp.localize_utc(datetime(2031, 12, 1, 23, 59)), lambda tzp: tzp.localize(datetime(1984, 1, 13, 13, 1), "Europe/Athens"), ] ) def exdate(request, tzp): """Possible values for an exdate.""" return request.param(tzp) def test_no_exdates_by_default(c_exdate): """We expect no exdate by default.""" assert c_exdate.exdates == [] def test_set_and_retrieve_exdate(exdate, c_exdate): """Set the attribute and get the value.""" c_exdate.add("exdate", [exdate]) result = [exdate] assert c_exdate.exdates == result def test_set_and_retrieve_exdates_in_list(exdate, c_exdate): """Set the attribute and get the value.""" c_exdate.add("exdate", [exdate, exdate]) result = [exdate, exdate] assert c_exdate.exdates == result def test_set_and_retrieve_exdates_twice(exdate, c_exdate): """Set the attribute and get the value.""" c_exdate.add("exdate", [exdate]) c_exdate.add("exdate", [exdate]) result = [exdate, exdate] assert c_exdate.exdates == result icalendar-6.3.1/src/icalendar/tests/attr/test_rdate.py000066400000000000000000000062751501302773300230130ustar00rootroot00000000000000"""This tests the RDATE property. """ from __future__ import annotations from datetime import date, datetime from pprint import pprint from typing import Union import pytest from icalendar import ( Calendar, Event, Journal, TimezoneDaylight, TimezoneStandard, Todo, ) C_RDATE = Union[Event, Todo, Journal, TimezoneDaylight, TimezoneStandard] @pytest.fixture(params = [Event, Todo, Journal, TimezoneDaylight, TimezoneStandard]) def c_rdate(request) -> C_RDATE: """Return a component that uses RDATE.""" return request.param() @pytest.fixture( params=[ lambda _tzp: date(2019, 10, 11), lambda _tzp: datetime(2000, 1, 13, 12, 1), lambda tzp: tzp.localize_utc(datetime(2031, 12, 1, 23, 59)), lambda tzp: tzp.localize(datetime(1984, 1, 13, 13, 1), "Europe/Athens"), lambda _tzp: ((datetime(2000, 1, 13, 12, 1), datetime(2000, 1, 13, 12, 2))), lambda tzp: ((tzp.localize_utc(datetime(2001, 1, 13, 12, 1)), tzp.localize_utc(datetime(2001, 1, 13, 12, 2)))), ] ) def rdate(request, tzp): """Possible values for an rdate.""" return request.param(tzp) def test_no_rdates_by_default(c_rdate): """We expect no rdate by default.""" assert c_rdate.rdates == [] def test_set_and_retrieve_rdate(rdate, c_rdate): """Set the attribute and get the value.""" c_rdate.add("RDATE", [rdate]) result = [rdate if isinstance(rdate, tuple) else (rdate, None)] assert c_rdate.rdates == result def test_get_example_0(calendars): """Test an example rdate.""" cal : Calendar = calendars.rfc_5545_RDATE_example event = cal.events[0] assert event.rdates == [(datetime(1997, 7, 14, 12, 30), None)] def test_get_example_1(calendars, tzp): """Test an example rdate.""" cal : Calendar = calendars.rfc_5545_RDATE_example event = cal.events[1] assert event.rdates == [(tzp.localize_utc(datetime(1997, 7, 14, 12, 30)), None)] def test_get_example_2(calendars, tzp): """Test an example rdate.""" cal : Calendar = calendars.rfc_5545_RDATE_example event = cal.events[2] assert event.rdates == [(tzp.localize(datetime(1997, 7, 14, 8, 30), "America/New_York"),None)] def test_get_example_3(calendars, tzp): """Test an example rdate.""" cal : Calendar = calendars.rfc_5545_RDATE_example event = cal.events[3] rdates_3 = [ (tzp.localize_utc(datetime(1996, 4, 3, 2)), tzp.localize_utc(datetime(1996, 4, 3, 4))), (tzp.localize_utc(datetime(1996, 4, 4, 1)), tzp.localize_utc(datetime(1996, 4, 4, 4))), ] pprint(event.rdates) pprint(rdates_3) assert event.rdates == rdates_3 def d(i:int) -> tuple[date, None]: s = str(i) return (date(int(s[:4]), int(s[4:6].lstrip("0")), int(s[6:].lstrip("0"))), None) RDATES_4 = list(map(d, ( 19970101, 19970120, 19970217, 19970421, 19970526, 19970704, 19970901, 19971014, 19971128, 19971129, 19971225, ))) def test_get_example_4(calendars, tzp): """Test an example rdate.""" cal : Calendar = calendars.rfc_5545_RDATE_example event = cal.events[4] pprint(event.rdates) pprint(RDATES_4) assert event.rdates == RDATES_4 icalendar-6.3.1/src/icalendar/tests/attr/test_rrule.py000066400000000000000000000024041501302773300230330ustar00rootroot00000000000000"""Test getting the rrules from a component.""" import pytest from icalendar import ( Component, Event, Journal, TimezoneDaylight, TimezoneStandard, Todo, vRecur, ) RRULE_0 = vRecur.from_ical("FREQ=DAILY;COUNT=10") RRULE_1 = vRecur.from_ical("FREQ=DAILY;UNTIL=19971224T000000Z") RRULE_2 = vRecur.from_ical("FREQ=DAILY;INTERVAL=2") RRULE_3 = vRecur.from_ical("FREQ=DAILY;INTERVAL=10;COUNT=5") RRULE_4 = vRecur.from_ical("FREQ=YEARLY;UNTIL=20000131T140000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA") @pytest.fixture(params=[RRULE_0, RRULE_1, RRULE_2, RRULE_3, RRULE_4]) def rrule(request) -> str: """An rrule.""" return request.param @pytest.fixture(params = [Event, Todo, Journal, TimezoneDaylight, TimezoneStandard]) def c_rrule(request) -> Component: """Return a component that uses RDATE.""" return request.param() def test_no_rrules_by_default(c_rrule): """We expect no rdate by default.""" assert c_rrule.rrules == [] def test_one_rrule(c_rrule, rrule): """Add one rrule.""" c_rrule.add("rrule", rrule) assert c_rrule.rrules == [rrule] def test_two_rrules(c_rrule, rrule): """Add two rrules.""" c_rrule.add("rrule", rrule) c_rrule.add("rrule", RRULE_3) assert c_rrule.rrules == [rrule, RRULE_3] icalendar-6.3.1/src/icalendar/tests/calendars/000077500000000000000000000000001501302773300212535ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/calendars/alarm_etar_future.ics000066400000000000000000000120721501302773300254560ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Offline Calendar//iCal Import/Export 2.8.1//EN VERSION:2.0 METHOD:PUBLISH CALSCALE:GREGORIAN BEGIN:VTIMEZONE TZID:Europe/London TZURL:http://tzurl.org/zoneinfo/Europe/London X-LIC-LOCATION:Europe/London BEGIN:DAYLIGHT TZOFFSETFROM:+0000 TZOFFSETTO:+0100 TZNAME:BST DTSTART:19810329T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0100 TZOFFSETTO:+0000 TZNAME:GMT DTSTART:19961027T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD BEGIN:STANDARD TZOFFSETFROM:-000115 TZOFFSETTO:+0000 TZNAME:GMT DTSTART:18471201T000115 RDATE:18471201T000115 END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:+0000 TZOFFSETTO:+0100 TZNAME:BST DTSTART:19160521T020000 RDATE:19160521T020000 RDATE:19170408T020000 RDATE:19180324T020000 RDATE:19190330T020000 RDATE:19200328T020000 RDATE:19210403T020000 RDATE:19220326T020000 RDATE:19230422T020000 RDATE:19240413T020000 RDATE:19250419T020000 RDATE:19260418T020000 RDATE:19270410T020000 RDATE:19280422T020000 RDATE:19290421T020000 RDATE:19300413T020000 RDATE:19310419T020000 RDATE:19320417T020000 RDATE:19330409T020000 RDATE:19340422T020000 RDATE:19350414T020000 RDATE:19360419T020000 RDATE:19370418T020000 RDATE:19380410T020000 RDATE:19390416T020000 RDATE:19400225T020000 RDATE:19460414T020000 RDATE:19470316T020000 RDATE:19480314T020000 RDATE:19490403T020000 RDATE:19500416T020000 RDATE:19510415T020000 RDATE:19520420T020000 RDATE:19530419T020000 RDATE:19540411T020000 RDATE:19550417T020000 RDATE:19560422T020000 RDATE:19570414T020000 RDATE:19580420T020000 RDATE:19590419T020000 RDATE:19600410T020000 RDATE:19610326T020000 RDATE:19620325T020000 RDATE:19630331T020000 RDATE:19640322T020000 RDATE:19650321T020000 RDATE:19660320T020000 RDATE:19670319T020000 RDATE:19680218T020000 RDATE:19720319T020000 RDATE:19730318T020000 RDATE:19740317T020000 RDATE:19750316T020000 RDATE:19760321T020000 RDATE:19770320T020000 RDATE:19780319T020000 RDATE:19790318T020000 RDATE:19800316T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0100 TZOFFSETTO:+0000 TZNAME:GMT DTSTART:19161001T030000 RDATE:19161001T030000 RDATE:19170917T030000 RDATE:19180930T030000 RDATE:19190929T030000 RDATE:19201025T030000 RDATE:19211003T030000 RDATE:19221008T030000 RDATE:19230916T030000 RDATE:19240921T030000 RDATE:19251004T030000 RDATE:19261003T030000 RDATE:19271002T030000 RDATE:19281007T030000 RDATE:19291006T030000 RDATE:19301005T030000 RDATE:19311004T030000 RDATE:19321002T030000 RDATE:19331008T030000 RDATE:19341007T030000 RDATE:19351006T030000 RDATE:19361004T030000 RDATE:19371003T030000 RDATE:19381002T030000 RDATE:19391119T030000 RDATE:19451007T030000 RDATE:19461006T030000 RDATE:19471102T030000 RDATE:19481031T030000 RDATE:19491030T030000 RDATE:19501022T030000 RDATE:19511021T030000 RDATE:19521026T030000 RDATE:19531004T030000 RDATE:19541003T030000 RDATE:19551002T030000 RDATE:19561007T030000 RDATE:19571006T030000 RDATE:19581005T030000 RDATE:19591004T030000 RDATE:19601002T030000 RDATE:19611029T030000 RDATE:19621028T030000 RDATE:19631027T030000 RDATE:19641025T030000 RDATE:19651024T030000 RDATE:19661023T030000 RDATE:19671029T030000 RDATE:19711031T030000 RDATE:19721029T030000 RDATE:19731028T030000 RDATE:19741027T030000 RDATE:19751026T030000 RDATE:19761024T030000 RDATE:19771023T030000 RDATE:19781029T030000 RDATE:19791028T030000 RDATE:19801026T030000 RDATE:19811025T020000 RDATE:19821024T020000 RDATE:19831023T020000 RDATE:19841028T020000 RDATE:19851027T020000 RDATE:19861026T020000 RDATE:19871025T020000 RDATE:19881023T020000 RDATE:19891029T020000 RDATE:19901028T020000 RDATE:19911027T020000 RDATE:19921025T020000 RDATE:19931024T020000 RDATE:19941023T020000 RDATE:19951022T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:+0100 TZOFFSETTO:+0200 TZNAME:BDST DTSTART:19410504T010000 RDATE:19410504T010000 RDATE:19420405T010000 RDATE:19430404T010000 RDATE:19440402T010000 RDATE:19450402T010000 RDATE:19470413T010000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETFROM:+0200 TZOFFSETTO:+0100 TZNAME:BST DTSTART:19410810T030000 RDATE:19410810T030000 RDATE:19420809T030000 RDATE:19430815T030000 RDATE:19440917T030000 RDATE:19450715T030000 RDATE:19470810T030000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0100 TZOFFSETTO:+0100 TZNAME:BST DTSTART:19681026T230000 RDATE:19681026T230000 END:STANDARD BEGIN:STANDARD TZOFFSETFROM:+0000 TZOFFSETTO:+0000 TZNAME:GMT DTSTART:19960101T000000 RDATE:19960101T000000 END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTAMP:20241005T112701Z UID:17281276213728ad54d03afa44d1ca60b8c52afaece9e@sufficientlysecure.org SUMMARY:event with alarms android STATUS:CONFIRMED DTSTART;TZID=Europe/London:20241005T130000 DTEND:20241005T130000Z LAST-MODIFIED:20241005T112701Z BEGIN:VALARM TRIGGER:-PT30M ACTION:DISPLAY DESCRIPTION:event with alarms android END:VALARM BEGIN:VALARM TRIGGER:-PT25M ACTION:DISPLAY DESCRIPTION:event with alarms android END:VALARM BEGIN:VALARM TRIGGER:-PT5M ACTION:DISPLAY DESCRIPTION:event with alarms android END:VALARM END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/alarm_etar_notification.ics000066400000000000000000000120721501302773300266320ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Offline Calendar//iCal Import/Export 2.8.1//EN VERSION:2.0 METHOD:PUBLISH CALSCALE:GREGORIAN BEGIN:VTIMEZONE TZID:Europe/London TZURL:http://tzurl.org/zoneinfo/Europe/London X-LIC-LOCATION:Europe/London BEGIN:DAYLIGHT TZOFFSETFROM:+0000 TZOFFSETTO:+0100 TZNAME:BST DTSTART:19810329T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0100 TZOFFSETTO:+0000 TZNAME:GMT DTSTART:19961027T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD BEGIN:STANDARD TZOFFSETFROM:-000115 TZOFFSETTO:+0000 TZNAME:GMT DTSTART:18471201T000115 RDATE:18471201T000115 END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:+0000 TZOFFSETTO:+0100 TZNAME:BST DTSTART:19160521T020000 RDATE:19160521T020000 RDATE:19170408T020000 RDATE:19180324T020000 RDATE:19190330T020000 RDATE:19200328T020000 RDATE:19210403T020000 RDATE:19220326T020000 RDATE:19230422T020000 RDATE:19240413T020000 RDATE:19250419T020000 RDATE:19260418T020000 RDATE:19270410T020000 RDATE:19280422T020000 RDATE:19290421T020000 RDATE:19300413T020000 RDATE:19310419T020000 RDATE:19320417T020000 RDATE:19330409T020000 RDATE:19340422T020000 RDATE:19350414T020000 RDATE:19360419T020000 RDATE:19370418T020000 RDATE:19380410T020000 RDATE:19390416T020000 RDATE:19400225T020000 RDATE:19460414T020000 RDATE:19470316T020000 RDATE:19480314T020000 RDATE:19490403T020000 RDATE:19500416T020000 RDATE:19510415T020000 RDATE:19520420T020000 RDATE:19530419T020000 RDATE:19540411T020000 RDATE:19550417T020000 RDATE:19560422T020000 RDATE:19570414T020000 RDATE:19580420T020000 RDATE:19590419T020000 RDATE:19600410T020000 RDATE:19610326T020000 RDATE:19620325T020000 RDATE:19630331T020000 RDATE:19640322T020000 RDATE:19650321T020000 RDATE:19660320T020000 RDATE:19670319T020000 RDATE:19680218T020000 RDATE:19720319T020000 RDATE:19730318T020000 RDATE:19740317T020000 RDATE:19750316T020000 RDATE:19760321T020000 RDATE:19770320T020000 RDATE:19780319T020000 RDATE:19790318T020000 RDATE:19800316T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0100 TZOFFSETTO:+0000 TZNAME:GMT DTSTART:19161001T030000 RDATE:19161001T030000 RDATE:19170917T030000 RDATE:19180930T030000 RDATE:19190929T030000 RDATE:19201025T030000 RDATE:19211003T030000 RDATE:19221008T030000 RDATE:19230916T030000 RDATE:19240921T030000 RDATE:19251004T030000 RDATE:19261003T030000 RDATE:19271002T030000 RDATE:19281007T030000 RDATE:19291006T030000 RDATE:19301005T030000 RDATE:19311004T030000 RDATE:19321002T030000 RDATE:19331008T030000 RDATE:19341007T030000 RDATE:19351006T030000 RDATE:19361004T030000 RDATE:19371003T030000 RDATE:19381002T030000 RDATE:19391119T030000 RDATE:19451007T030000 RDATE:19461006T030000 RDATE:19471102T030000 RDATE:19481031T030000 RDATE:19491030T030000 RDATE:19501022T030000 RDATE:19511021T030000 RDATE:19521026T030000 RDATE:19531004T030000 RDATE:19541003T030000 RDATE:19551002T030000 RDATE:19561007T030000 RDATE:19571006T030000 RDATE:19581005T030000 RDATE:19591004T030000 RDATE:19601002T030000 RDATE:19611029T030000 RDATE:19621028T030000 RDATE:19631027T030000 RDATE:19641025T030000 RDATE:19651024T030000 RDATE:19661023T030000 RDATE:19671029T030000 RDATE:19711031T030000 RDATE:19721029T030000 RDATE:19731028T030000 RDATE:19741027T030000 RDATE:19751026T030000 RDATE:19761024T030000 RDATE:19771023T030000 RDATE:19781029T030000 RDATE:19791028T030000 RDATE:19801026T030000 RDATE:19811025T020000 RDATE:19821024T020000 RDATE:19831023T020000 RDATE:19841028T020000 RDATE:19851027T020000 RDATE:19861026T020000 RDATE:19871025T020000 RDATE:19881023T020000 RDATE:19891029T020000 RDATE:19901028T020000 RDATE:19911027T020000 RDATE:19921025T020000 RDATE:19931024T020000 RDATE:19941023T020000 RDATE:19951022T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:+0100 TZOFFSETTO:+0200 TZNAME:BDST DTSTART:19410504T010000 RDATE:19410504T010000 RDATE:19420405T010000 RDATE:19430404T010000 RDATE:19440402T010000 RDATE:19450402T010000 RDATE:19470413T010000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETFROM:+0200 TZOFFSETTO:+0100 TZNAME:BST DTSTART:19410810T030000 RDATE:19410810T030000 RDATE:19420809T030000 RDATE:19430815T030000 RDATE:19440917T030000 RDATE:19450715T030000 RDATE:19470810T030000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0100 TZOFFSETTO:+0100 TZNAME:BST DTSTART:19681026T230000 RDATE:19681026T230000 END:STANDARD BEGIN:STANDARD TZOFFSETFROM:+0000 TZOFFSETTO:+0000 TZNAME:GMT DTSTART:19960101T000000 RDATE:19960101T000000 END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTAMP:20241005T113036Z UID:17281276213728ad54d03afa44d1ca60b8c52afaece9e@sufficientlysecure.org SUMMARY:event with alarms android STATUS:CONFIRMED DTSTART;TZID=Europe/London:20241005T130000 DTEND:20241005T130000Z LAST-MODIFIED:20241005T112701Z BEGIN:VALARM TRIGGER:-PT30M ACTION:DISPLAY DESCRIPTION:event with alarms android END:VALARM BEGIN:VALARM TRIGGER:-PT25M ACTION:DISPLAY DESCRIPTION:event with alarms android END:VALARM BEGIN:VALARM TRIGGER:-PT5M ACTION:DISPLAY DESCRIPTION:event with alarms android END:VALARM END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/alarm_etar_notification_clicked.ics000066400000000000000000000116011501302773300303050ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Offline Calendar//iCal Import/Export 2.8.1//EN VERSION:2.0 METHOD:PUBLISH CALSCALE:GREGORIAN BEGIN:VTIMEZONE TZID:Europe/London TZURL:http://tzurl.org/zoneinfo/Europe/London X-LIC-LOCATION:Europe/London BEGIN:DAYLIGHT TZOFFSETFROM:+0000 TZOFFSETTO:+0100 TZNAME:BST DTSTART:19810329T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0100 TZOFFSETTO:+0000 TZNAME:GMT DTSTART:19961027T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD BEGIN:STANDARD TZOFFSETFROM:-000115 TZOFFSETTO:+0000 TZNAME:GMT DTSTART:18471201T000115 RDATE:18471201T000115 END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:+0000 TZOFFSETTO:+0100 TZNAME:BST DTSTART:19160521T020000 RDATE:19160521T020000 RDATE:19170408T020000 RDATE:19180324T020000 RDATE:19190330T020000 RDATE:19200328T020000 RDATE:19210403T020000 RDATE:19220326T020000 RDATE:19230422T020000 RDATE:19240413T020000 RDATE:19250419T020000 RDATE:19260418T020000 RDATE:19270410T020000 RDATE:19280422T020000 RDATE:19290421T020000 RDATE:19300413T020000 RDATE:19310419T020000 RDATE:19320417T020000 RDATE:19330409T020000 RDATE:19340422T020000 RDATE:19350414T020000 RDATE:19360419T020000 RDATE:19370418T020000 RDATE:19380410T020000 RDATE:19390416T020000 RDATE:19400225T020000 RDATE:19460414T020000 RDATE:19470316T020000 RDATE:19480314T020000 RDATE:19490403T020000 RDATE:19500416T020000 RDATE:19510415T020000 RDATE:19520420T020000 RDATE:19530419T020000 RDATE:19540411T020000 RDATE:19550417T020000 RDATE:19560422T020000 RDATE:19570414T020000 RDATE:19580420T020000 RDATE:19590419T020000 RDATE:19600410T020000 RDATE:19610326T020000 RDATE:19620325T020000 RDATE:19630331T020000 RDATE:19640322T020000 RDATE:19650321T020000 RDATE:19660320T020000 RDATE:19670319T020000 RDATE:19680218T020000 RDATE:19720319T020000 RDATE:19730318T020000 RDATE:19740317T020000 RDATE:19750316T020000 RDATE:19760321T020000 RDATE:19770320T020000 RDATE:19780319T020000 RDATE:19790318T020000 RDATE:19800316T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0100 TZOFFSETTO:+0000 TZNAME:GMT DTSTART:19161001T030000 RDATE:19161001T030000 RDATE:19170917T030000 RDATE:19180930T030000 RDATE:19190929T030000 RDATE:19201025T030000 RDATE:19211003T030000 RDATE:19221008T030000 RDATE:19230916T030000 RDATE:19240921T030000 RDATE:19251004T030000 RDATE:19261003T030000 RDATE:19271002T030000 RDATE:19281007T030000 RDATE:19291006T030000 RDATE:19301005T030000 RDATE:19311004T030000 RDATE:19321002T030000 RDATE:19331008T030000 RDATE:19341007T030000 RDATE:19351006T030000 RDATE:19361004T030000 RDATE:19371003T030000 RDATE:19381002T030000 RDATE:19391119T030000 RDATE:19451007T030000 RDATE:19461006T030000 RDATE:19471102T030000 RDATE:19481031T030000 RDATE:19491030T030000 RDATE:19501022T030000 RDATE:19511021T030000 RDATE:19521026T030000 RDATE:19531004T030000 RDATE:19541003T030000 RDATE:19551002T030000 RDATE:19561007T030000 RDATE:19571006T030000 RDATE:19581005T030000 RDATE:19591004T030000 RDATE:19601002T030000 RDATE:19611029T030000 RDATE:19621028T030000 RDATE:19631027T030000 RDATE:19641025T030000 RDATE:19651024T030000 RDATE:19661023T030000 RDATE:19671029T030000 RDATE:19711031T030000 RDATE:19721029T030000 RDATE:19731028T030000 RDATE:19741027T030000 RDATE:19751026T030000 RDATE:19761024T030000 RDATE:19771023T030000 RDATE:19781029T030000 RDATE:19791028T030000 RDATE:19801026T030000 RDATE:19811025T020000 RDATE:19821024T020000 RDATE:19831023T020000 RDATE:19841028T020000 RDATE:19851027T020000 RDATE:19861026T020000 RDATE:19871025T020000 RDATE:19881023T020000 RDATE:19891029T020000 RDATE:19901028T020000 RDATE:19911027T020000 RDATE:19921025T020000 RDATE:19931024T020000 RDATE:19941023T020000 RDATE:19951022T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:+0100 TZOFFSETTO:+0200 TZNAME:BDST DTSTART:19410504T010000 RDATE:19410504T010000 RDATE:19420405T010000 RDATE:19430404T010000 RDATE:19440402T010000 RDATE:19450402T010000 RDATE:19470413T010000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETFROM:+0200 TZOFFSETTO:+0100 TZNAME:BST DTSTART:19410810T030000 RDATE:19410810T030000 RDATE:19420809T030000 RDATE:19430815T030000 RDATE:19440917T030000 RDATE:19450715T030000 RDATE:19470810T030000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0100 TZOFFSETTO:+0100 TZNAME:BST DTSTART:19681026T230000 RDATE:19681026T230000 END:STANDARD BEGIN:STANDARD TZOFFSETFROM:+0000 TZOFFSETTO:+0000 TZNAME:GMT DTSTART:19960101T000000 RDATE:19960101T000000 END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTAMP:20241005T130738Z UID:17281336589228ad54d03afa44d1ca60b8c52afaece9e@sufficientlysecure.org SUMMARY:event with alarm acknowledged STATUS:CONFIRMED DTSTART;TZID=Europe/London:20241005T141700 DTEND:20241005T141700Z LAST-MODIFIED:20241005T130738Z BEGIN:VALARM TRIGGER:-PT10M ACTION:DISPLAY DESCRIPTION:event with alarm acknowledged END:VALARM END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/alarm_google_acknowledged.ics000066400000000000000000000024561501302773300271210ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:Nicco Kunzmann X-WR-TIMEZONE:Europe/London BEGIN:VTIMEZONE TZID:Europe/Berlin X-LIC-LOCATION:Europe/Berlin BEGIN:DAYLIGHT TZOFFSETFROM:+0100 TZOFFSETTO:+0200 TZNAME:GMT+2 DTSTART:19700329T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0200 TZOFFSETTO:+0100 TZNAME:GMT+1 DTSTART:19701025T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTART:20241004T181500Z DTEND:20241004T190000Z DTSTAMP:20241004T180026Z UID:79fs7pkqvht9m5igs0vjv1sfra@google.com CREATED:20241004T175920Z LAST-MODIFIED:20241004T175928Z SEQUENCE:0 STATUS:CONFIRMED SUMMARY:event with alarms TRANSP:OPAQUE BEGIN:VALARM ACTION:DISPLAY TRIGGER:-P0DT0H10M0S DESCRIPTION:This is an event reminder END:VALARM BEGIN:VALARM ACTION:DISPLAY TRIGGER:-P0DT0H14M0S DESCRIPTION:This is an event reminder END:VALARM BEGIN:VALARM ACTION:EMAIL ATTENDEE:mailto:niccokunzmann@googlemail.com TRIGGER:-P0DT0H15M0S DESCRIPTION:This is an event reminder SUMMARY:Alarm notification END:VALARM BEGIN:VALARM ACTION:DISPLAY TRIGGER:-P0DT0H15M0S DESCRIPTION:This is an event reminder END:VALARM END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/alarm_google_future.ics000066400000000000000000000024561501302773300260040ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:Nicco Kunzmann X-WR-TIMEZONE:Europe/London BEGIN:VTIMEZONE TZID:Europe/Berlin X-LIC-LOCATION:Europe/Berlin BEGIN:DAYLIGHT TZOFFSETFROM:+0100 TZOFFSETTO:+0200 TZNAME:GMT+2 DTSTART:19700329T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0200 TZOFFSETTO:+0100 TZNAME:GMT+1 DTSTART:19701025T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTART:20241004T181500Z DTEND:20241004T190000Z DTSTAMP:20241004T175945Z UID:79fs7pkqvht9m5igs0vjv1sfra@google.com CREATED:20241004T175920Z LAST-MODIFIED:20241004T175928Z SEQUENCE:0 STATUS:CONFIRMED SUMMARY:event with alarms TRANSP:OPAQUE BEGIN:VALARM ACTION:DISPLAY TRIGGER:-P0DT0H10M0S DESCRIPTION:This is an event reminder END:VALARM BEGIN:VALARM ACTION:DISPLAY TRIGGER:-P0DT0H14M0S DESCRIPTION:This is an event reminder END:VALARM BEGIN:VALARM ACTION:EMAIL ATTENDEE:mailto:niccokunzmann@googlemail.com TRIGGER:-P0DT0H15M0S DESCRIPTION:This is an event reminder SUMMARY:Alarm notification END:VALARM BEGIN:VALARM ACTION:DISPLAY TRIGGER:-P0DT0H15M0S DESCRIPTION:This is an event reminder END:VALARM END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/alarm_thunderbird_2_future.ics000066400000000000000000000335541501302773300272660ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:Europe/London X-TZINFO:Europe/London[2024a] BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:-000115 TZNAME:Europe/London(STD) DTSTART:18471201T000000 RDATE:18471201T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19160521T020000 RDATE:19160521T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19161001T030000 RDATE:19161001T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19170408T020000 RDATE:19170408T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19170917T030000 RDATE:19170917T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19180324T020000 RDATE:19180324T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19180930T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19190330T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19201025T030000 RDATE:19201025T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19210403T020000 RDATE:19210403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19211003T030000 RDATE:19211003T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19220326T020000 RDATE:19220326T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19221008T030000 RDATE:19221008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19230422T020000 RDATE:19230422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19240413T020000 RDATE:19240413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19230916T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19250419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19270410T020000 RDATE:19270410T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19280422T020000 RDATE:19280422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19290421T020000 RDATE:19290421T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19300413T020000 RDATE:19300413T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19310419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19251004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19330409T020000 RDATE:19330409T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19331008T030000 RDATE:19331008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19340422T020000 RDATE:19340422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19350414T020000 RDATE:19350414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19360419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19380410T020000 RDATE:19380410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19341007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19390416T020000 RDATE:19390416T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19400225T020000 RDATE:19400225T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19410504T020000 RDATE:19410504T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19410810T030000 RDATE:19410810T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19420405T020000 RDATE:19420405T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19420809T030000 RDATE:19420809T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19430404T020000 RDATE:19430404T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19430815T030000 RDATE:19430815T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19440402T020000 RDATE:19440402T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19440917T030000 RDATE:19440917T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19450402T020000 RDATE:19450402T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19391119T030000 RDATE:19391119T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19450715T030000 RDATE:19450715T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19460414T020000 RDATE:19460414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19470316T020000 RDATE:19470316T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19470413T020000 RDATE:19470413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19451007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19470810T030000 RDATE:19470810T030000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19471102T030000 RDATE:19471102T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19480314T020000 RDATE:19480314T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19490403T020000 RDATE:19490403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19481031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19501022T030000 RDATE:19501022T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19511021T030000 RDATE:19511021T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19521026T030000 RDATE:19521026T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19500416T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19540411T020000 RDATE:19540411T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19550417T020000 RDATE:19550417T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19560422T020000 RDATE:19560422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19570414T020000 RDATE:19570414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19580420T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19600410T020000 RDATE:19600410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19531004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19610326T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19640322T020000 RDATE:19640322T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19611029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19651024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19650321T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19671029T030000 RDATE:19671029T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+010000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19681027T000000 RDATE:19681027T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19680218T020000 RDATE:19680218T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19711031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19761024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19720319T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19781029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19811025T020000 RDATE:19811025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19821024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19841028T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19881023T020000 RDATE:19881023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19891029T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19931024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19810329T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19961027T020000 RDATE:19961027T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:(DST) DTSTART:19970330T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:(STD) DTSTART:19971026T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD END:VTIMEZONE BEGIN:VEVENT CREATED:20241023T173412Z LAST-MODIFIED:20241023T173453Z DTSTAMP:20241023T173453Z UID:731b9b91-cf72-499b-bbc9-c53c28e21fc7 SUMMARY:event DTSTART;TZID=Europe/London:20241023T190000 DTEND;TZID=Europe/London:20241023T200000 TRANSP:OPAQUE X-MOZ-GENERATION:2 BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT1M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT24M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/alarm_thunderbird_2_notification_5_min_postponed.ics000066400000000000000000000336601501302773300336220ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:Europe/London X-TZINFO:Europe/London[2024a] BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:-000115 TZNAME:Europe/London(STD) DTSTART:18471201T000000 RDATE:18471201T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19160521T020000 RDATE:19160521T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19161001T030000 RDATE:19161001T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19170408T020000 RDATE:19170408T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19170917T030000 RDATE:19170917T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19180324T020000 RDATE:19180324T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19180930T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19190330T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19201025T030000 RDATE:19201025T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19210403T020000 RDATE:19210403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19211003T030000 RDATE:19211003T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19220326T020000 RDATE:19220326T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19221008T030000 RDATE:19221008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19230422T020000 RDATE:19230422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19240413T020000 RDATE:19240413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19230916T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19250419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19270410T020000 RDATE:19270410T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19280422T020000 RDATE:19280422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19290421T020000 RDATE:19290421T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19300413T020000 RDATE:19300413T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19310419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19251004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19330409T020000 RDATE:19330409T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19331008T030000 RDATE:19331008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19340422T020000 RDATE:19340422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19350414T020000 RDATE:19350414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19360419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19380410T020000 RDATE:19380410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19341007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19390416T020000 RDATE:19390416T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19400225T020000 RDATE:19400225T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19410504T020000 RDATE:19410504T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19410810T030000 RDATE:19410810T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19420405T020000 RDATE:19420405T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19420809T030000 RDATE:19420809T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19430404T020000 RDATE:19430404T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19430815T030000 RDATE:19430815T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19440402T020000 RDATE:19440402T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19440917T030000 RDATE:19440917T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19450402T020000 RDATE:19450402T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19391119T030000 RDATE:19391119T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19450715T030000 RDATE:19450715T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19460414T020000 RDATE:19460414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19470316T020000 RDATE:19470316T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19470413T020000 RDATE:19470413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19451007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19470810T030000 RDATE:19470810T030000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19471102T030000 RDATE:19471102T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19480314T020000 RDATE:19480314T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19490403T020000 RDATE:19490403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19481031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19501022T030000 RDATE:19501022T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19511021T030000 RDATE:19511021T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19521026T030000 RDATE:19521026T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19500416T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19540411T020000 RDATE:19540411T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19550417T020000 RDATE:19550417T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19560422T020000 RDATE:19560422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19570414T020000 RDATE:19570414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19580420T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19600410T020000 RDATE:19600410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19531004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19610326T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19640322T020000 RDATE:19640322T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19611029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19651024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19650321T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19671029T030000 RDATE:19671029T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+010000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19681027T000000 RDATE:19681027T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19680218T020000 RDATE:19680218T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19711031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19761024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19720319T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19781029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19811025T020000 RDATE:19811025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19821024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19841028T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19881023T020000 RDATE:19881023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19891029T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19931024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19810329T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19961027T020000 RDATE:19961027T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:(DST) DTSTART:19970330T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:(STD) DTSTART:19971026T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD END:VTIMEZONE BEGIN:VEVENT CREATED:20241023T173412Z LAST-MODIFIED:20241023T173630Z DTSTAMP:20241023T173630Z UID:731b9b91-cf72-499b-bbc9-c53c28e21fc7 SUMMARY:event X-MOZ-LASTACK:20241023T173630Z DTSTART;TZID=Europe/London:20241023T190000 DTEND;TZID=Europe/London:20241023T200000 TRANSP:OPAQUE X-MOZ-GENERATION:3 X-MOZ-SNOOZE-TIME:20241023T174130Z BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT1M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT24M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM END:VEVENT END:VCALENDAR alarm_thunderbird_2_notification_5_min_postponed_and_closed.ics000066400000000000000000000336141501302773300357150ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/calendarsBEGIN:VCALENDAR PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:Europe/London X-TZINFO:Europe/London[2024a] BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:-000115 TZNAME:Europe/London(STD) DTSTART:18471201T000000 RDATE:18471201T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19160521T020000 RDATE:19160521T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19161001T030000 RDATE:19161001T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19170408T020000 RDATE:19170408T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19170917T030000 RDATE:19170917T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19180324T020000 RDATE:19180324T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19180930T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19190330T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19201025T030000 RDATE:19201025T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19210403T020000 RDATE:19210403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19211003T030000 RDATE:19211003T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19220326T020000 RDATE:19220326T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19221008T030000 RDATE:19221008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19230422T020000 RDATE:19230422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19240413T020000 RDATE:19240413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19230916T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19250419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19270410T020000 RDATE:19270410T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19280422T020000 RDATE:19280422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19290421T020000 RDATE:19290421T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19300413T020000 RDATE:19300413T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19310419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19251004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19330409T020000 RDATE:19330409T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19331008T030000 RDATE:19331008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19340422T020000 RDATE:19340422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19350414T020000 RDATE:19350414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19360419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19380410T020000 RDATE:19380410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19341007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19390416T020000 RDATE:19390416T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19400225T020000 RDATE:19400225T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19410504T020000 RDATE:19410504T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19410810T030000 RDATE:19410810T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19420405T020000 RDATE:19420405T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19420809T030000 RDATE:19420809T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19430404T020000 RDATE:19430404T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19430815T030000 RDATE:19430815T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19440402T020000 RDATE:19440402T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19440917T030000 RDATE:19440917T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19450402T020000 RDATE:19450402T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19391119T030000 RDATE:19391119T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19450715T030000 RDATE:19450715T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19460414T020000 RDATE:19460414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19470316T020000 RDATE:19470316T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19470413T020000 RDATE:19470413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19451007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19470810T030000 RDATE:19470810T030000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19471102T030000 RDATE:19471102T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19480314T020000 RDATE:19480314T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19490403T020000 RDATE:19490403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19481031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19501022T030000 RDATE:19501022T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19511021T030000 RDATE:19511021T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19521026T030000 RDATE:19521026T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19500416T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19540411T020000 RDATE:19540411T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19550417T020000 RDATE:19550417T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19560422T020000 RDATE:19560422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19570414T020000 RDATE:19570414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19580420T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19600410T020000 RDATE:19600410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19531004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19610326T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19640322T020000 RDATE:19640322T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19611029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19651024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19650321T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19671029T030000 RDATE:19671029T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+010000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19681027T000000 RDATE:19681027T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19680218T020000 RDATE:19680218T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19711031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19761024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19720319T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19781029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19811025T020000 RDATE:19811025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19821024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19841028T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19881023T020000 RDATE:19881023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19891029T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19931024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19810329T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19961027T020000 RDATE:19961027T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:(DST) DTSTART:19970330T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:(STD) DTSTART:19971026T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD END:VTIMEZONE BEGIN:VEVENT CREATED:20241023T173412Z LAST-MODIFIED:20241023T174207Z DTSTAMP:20241023T174207Z UID:731b9b91-cf72-499b-bbc9-c53c28e21fc7 SUMMARY:event X-MOZ-LASTACK:20241023T174207Z DTSTART;TZID=Europe/London:20241023T190000 DTEND;TZID=Europe/London:20241023T200000 TRANSP:OPAQUE X-MOZ-GENERATION:4 BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT1M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT24M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM END:VEVENT END:VCALENDAR alarm_thunderbird_2_notification_5_min_postponed_and_popped_up.ics000066400000000000000000000336601501302773300364400ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/calendarsBEGIN:VCALENDAR PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:Europe/London X-TZINFO:Europe/London[2024a] BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:-000115 TZNAME:Europe/London(STD) DTSTART:18471201T000000 RDATE:18471201T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19160521T020000 RDATE:19160521T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19161001T030000 RDATE:19161001T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19170408T020000 RDATE:19170408T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19170917T030000 RDATE:19170917T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19180324T020000 RDATE:19180324T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19180930T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19190330T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19201025T030000 RDATE:19201025T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19210403T020000 RDATE:19210403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19211003T030000 RDATE:19211003T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19220326T020000 RDATE:19220326T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19221008T030000 RDATE:19221008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19230422T020000 RDATE:19230422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19240413T020000 RDATE:19240413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19230916T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19250419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19270410T020000 RDATE:19270410T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19280422T020000 RDATE:19280422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19290421T020000 RDATE:19290421T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19300413T020000 RDATE:19300413T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19310419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19251004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19330409T020000 RDATE:19330409T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19331008T030000 RDATE:19331008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19340422T020000 RDATE:19340422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19350414T020000 RDATE:19350414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19360419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19380410T020000 RDATE:19380410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19341007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19390416T020000 RDATE:19390416T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19400225T020000 RDATE:19400225T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19410504T020000 RDATE:19410504T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19410810T030000 RDATE:19410810T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19420405T020000 RDATE:19420405T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19420809T030000 RDATE:19420809T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19430404T020000 RDATE:19430404T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19430815T030000 RDATE:19430815T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19440402T020000 RDATE:19440402T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19440917T030000 RDATE:19440917T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19450402T020000 RDATE:19450402T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19391119T030000 RDATE:19391119T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19450715T030000 RDATE:19450715T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19460414T020000 RDATE:19460414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19470316T020000 RDATE:19470316T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19470413T020000 RDATE:19470413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19451007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19470810T030000 RDATE:19470810T030000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19471102T030000 RDATE:19471102T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19480314T020000 RDATE:19480314T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19490403T020000 RDATE:19490403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19481031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19501022T030000 RDATE:19501022T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19511021T030000 RDATE:19511021T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19521026T030000 RDATE:19521026T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19500416T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19540411T020000 RDATE:19540411T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19550417T020000 RDATE:19550417T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19560422T020000 RDATE:19560422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19570414T020000 RDATE:19570414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19580420T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19600410T020000 RDATE:19600410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19531004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19610326T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19640322T020000 RDATE:19640322T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19611029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19651024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19650321T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19671029T030000 RDATE:19671029T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+010000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19681027T000000 RDATE:19681027T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19680218T020000 RDATE:19680218T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19711031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19761024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19720319T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19781029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19811025T020000 RDATE:19811025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19821024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19841028T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19881023T020000 RDATE:19881023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19891029T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19931024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19810329T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19961027T020000 RDATE:19961027T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:(DST) DTSTART:19970330T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:(STD) DTSTART:19971026T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD END:VTIMEZONE BEGIN:VEVENT CREATED:20241023T173412Z LAST-MODIFIED:20241023T173630Z DTSTAMP:20241023T173630Z UID:731b9b91-cf72-499b-bbc9-c53c28e21fc7 SUMMARY:event X-MOZ-LASTACK:20241023T173630Z DTSTART;TZID=Europe/London:20241023T190000 DTEND;TZID=Europe/London:20241023T200000 TRANSP:OPAQUE X-MOZ-GENERATION:3 X-MOZ-SNOOZE-TIME:20241023T174130Z BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT1M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT24M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/alarm_thunderbird_2_notification_popped_up.ics000066400000000000000000000335541501302773300325150ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:Europe/London X-TZINFO:Europe/London[2024a] BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:-000115 TZNAME:Europe/London(STD) DTSTART:18471201T000000 RDATE:18471201T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19160521T020000 RDATE:19160521T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19161001T030000 RDATE:19161001T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19170408T020000 RDATE:19170408T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19170917T030000 RDATE:19170917T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19180324T020000 RDATE:19180324T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19180930T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19190330T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19201025T030000 RDATE:19201025T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19210403T020000 RDATE:19210403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19211003T030000 RDATE:19211003T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19220326T020000 RDATE:19220326T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19221008T030000 RDATE:19221008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19230422T020000 RDATE:19230422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19240413T020000 RDATE:19240413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19230916T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19250419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19270410T020000 RDATE:19270410T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19280422T020000 RDATE:19280422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19290421T020000 RDATE:19290421T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19300413T020000 RDATE:19300413T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19310419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19251004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19330409T020000 RDATE:19330409T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19331008T030000 RDATE:19331008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19340422T020000 RDATE:19340422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19350414T020000 RDATE:19350414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19360419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19380410T020000 RDATE:19380410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19341007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19390416T020000 RDATE:19390416T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19400225T020000 RDATE:19400225T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19410504T020000 RDATE:19410504T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19410810T030000 RDATE:19410810T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19420405T020000 RDATE:19420405T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19420809T030000 RDATE:19420809T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19430404T020000 RDATE:19430404T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19430815T030000 RDATE:19430815T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19440402T020000 RDATE:19440402T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19440917T030000 RDATE:19440917T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19450402T020000 RDATE:19450402T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19391119T030000 RDATE:19391119T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19450715T030000 RDATE:19450715T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19460414T020000 RDATE:19460414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19470316T020000 RDATE:19470316T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19470413T020000 RDATE:19470413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19451007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19470810T030000 RDATE:19470810T030000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19471102T030000 RDATE:19471102T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19480314T020000 RDATE:19480314T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19490403T020000 RDATE:19490403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19481031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19501022T030000 RDATE:19501022T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19511021T030000 RDATE:19511021T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19521026T030000 RDATE:19521026T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19500416T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19540411T020000 RDATE:19540411T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19550417T020000 RDATE:19550417T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19560422T020000 RDATE:19560422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19570414T020000 RDATE:19570414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19580420T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19600410T020000 RDATE:19600410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19531004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19610326T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19640322T020000 RDATE:19640322T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19611029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19651024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19650321T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19671029T030000 RDATE:19671029T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+010000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19681027T000000 RDATE:19681027T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19680218T020000 RDATE:19680218T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19711031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19761024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19720319T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19781029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19811025T020000 RDATE:19811025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19821024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19841028T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19881023T020000 RDATE:19881023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19891029T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19931024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19810329T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19961027T020000 RDATE:19961027T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:(DST) DTSTART:19970330T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:(STD) DTSTART:19971026T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD END:VTIMEZONE BEGIN:VEVENT CREATED:20241023T173412Z LAST-MODIFIED:20241023T173453Z DTSTAMP:20241023T173453Z UID:731b9b91-cf72-499b-bbc9-c53c28e21fc7 SUMMARY:event DTSTART;TZID=Europe/London:20241023T190000 DTEND;TZID=Europe/London:20241023T200000 TRANSP:OPAQUE X-MOZ-GENERATION:2 BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT1M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT24M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/alarm_thunderbird_closed.ics000066400000000000000000000336311501302773300270000ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:Europe/London X-TZINFO:Europe/London[2024a] BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:-000115 TZNAME:Europe/London(STD) DTSTART:18471201T000000 RDATE:18471201T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19160521T020000 RDATE:19160521T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19161001T030000 RDATE:19161001T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19170408T020000 RDATE:19170408T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19170917T030000 RDATE:19170917T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19180324T020000 RDATE:19180324T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19180930T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19190330T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19201025T030000 RDATE:19201025T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19210403T020000 RDATE:19210403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19211003T030000 RDATE:19211003T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19220326T020000 RDATE:19220326T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19221008T030000 RDATE:19221008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19230422T020000 RDATE:19230422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19240413T020000 RDATE:19240413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19230916T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19250419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19270410T020000 RDATE:19270410T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19280422T020000 RDATE:19280422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19290421T020000 RDATE:19290421T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19300413T020000 RDATE:19300413T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19310419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19251004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19330409T020000 RDATE:19330409T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19331008T030000 RDATE:19331008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19340422T020000 RDATE:19340422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19350414T020000 RDATE:19350414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19360419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19380410T020000 RDATE:19380410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19341007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19390416T020000 RDATE:19390416T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19400225T020000 RDATE:19400225T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19410504T020000 RDATE:19410504T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19410810T030000 RDATE:19410810T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19420405T020000 RDATE:19420405T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19420809T030000 RDATE:19420809T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19430404T020000 RDATE:19430404T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19430815T030000 RDATE:19430815T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19440402T020000 RDATE:19440402T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19440917T030000 RDATE:19440917T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19450402T020000 RDATE:19450402T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19391119T030000 RDATE:19391119T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19450715T030000 RDATE:19450715T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19460414T020000 RDATE:19460414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19470316T020000 RDATE:19470316T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19470413T020000 RDATE:19470413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19451007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19470810T030000 RDATE:19470810T030000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19471102T030000 RDATE:19471102T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19480314T020000 RDATE:19480314T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19490403T020000 RDATE:19490403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19481031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19501022T030000 RDATE:19501022T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19511021T030000 RDATE:19511021T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19521026T030000 RDATE:19521026T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19500416T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19540411T020000 RDATE:19540411T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19550417T020000 RDATE:19550417T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19560422T020000 RDATE:19560422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19570414T020000 RDATE:19570414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19580420T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19600410T020000 RDATE:19600410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19531004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19610326T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19640322T020000 RDATE:19640322T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19611029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19651024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19650321T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19671029T030000 RDATE:19671029T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+010000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19681027T000000 RDATE:19681027T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19680218T020000 RDATE:19680218T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19711031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19761024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19720319T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19781029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19811025T020000 RDATE:19811025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19821024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19841028T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19881023T020000 RDATE:19881023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19891029T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19931024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19810329T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19961027T020000 RDATE:19961027T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:(DST) DTSTART:19970330T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:(STD) DTSTART:19971026T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD END:VTIMEZONE BEGIN:VEVENT CREATED:20241023T131035Z LAST-MODIFIED:20241023T141941Z DTSTAMP:20241023T141941Z UID:b9a23b47-f109-4e7a-908c-75e925b27def SUMMARY:event with alarms X-MOZ-LASTACK:20241023T141941Z DTSTART;TZID=Europe/London:20241023T150000 DTEND;TZID=Europe/London:20241023T160000 TRANSP:OPAQUE X-MOZ-GENERATION:6 BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT15M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT45M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/alarm_thunderbird_future.ics000066400000000000000000000335711501302773300270440ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:Europe/London X-TZINFO:Europe/London[2024a] BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:-000115 TZNAME:Europe/London(STD) DTSTART:18471201T000000 RDATE:18471201T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19160521T020000 RDATE:19160521T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19161001T030000 RDATE:19161001T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19170408T020000 RDATE:19170408T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19170917T030000 RDATE:19170917T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19180324T020000 RDATE:19180324T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19180930T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19190330T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19201025T030000 RDATE:19201025T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19210403T020000 RDATE:19210403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19211003T030000 RDATE:19211003T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19220326T020000 RDATE:19220326T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19221008T030000 RDATE:19221008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19230422T020000 RDATE:19230422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19240413T020000 RDATE:19240413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19230916T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19250419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19270410T020000 RDATE:19270410T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19280422T020000 RDATE:19280422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19290421T020000 RDATE:19290421T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19300413T020000 RDATE:19300413T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19310419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19251004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19330409T020000 RDATE:19330409T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19331008T030000 RDATE:19331008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19340422T020000 RDATE:19340422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19350414T020000 RDATE:19350414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19360419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19380410T020000 RDATE:19380410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19341007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19390416T020000 RDATE:19390416T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19400225T020000 RDATE:19400225T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19410504T020000 RDATE:19410504T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19410810T030000 RDATE:19410810T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19420405T020000 RDATE:19420405T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19420809T030000 RDATE:19420809T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19430404T020000 RDATE:19430404T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19430815T030000 RDATE:19430815T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19440402T020000 RDATE:19440402T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19440917T030000 RDATE:19440917T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19450402T020000 RDATE:19450402T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19391119T030000 RDATE:19391119T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19450715T030000 RDATE:19450715T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19460414T020000 RDATE:19460414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19470316T020000 RDATE:19470316T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19470413T020000 RDATE:19470413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19451007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19470810T030000 RDATE:19470810T030000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19471102T030000 RDATE:19471102T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19480314T020000 RDATE:19480314T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19490403T020000 RDATE:19490403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19481031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19501022T030000 RDATE:19501022T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19511021T030000 RDATE:19511021T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19521026T030000 RDATE:19521026T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19500416T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19540411T020000 RDATE:19540411T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19550417T020000 RDATE:19550417T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19560422T020000 RDATE:19560422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19570414T020000 RDATE:19570414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19580420T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19600410T020000 RDATE:19600410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19531004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19610326T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19640322T020000 RDATE:19640322T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19611029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19651024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19650321T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19671029T030000 RDATE:19671029T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+010000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19681027T000000 RDATE:19681027T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19680218T020000 RDATE:19680218T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19711031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19761024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19720319T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19781029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19811025T020000 RDATE:19811025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19821024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19841028T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19881023T020000 RDATE:19881023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19891029T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19931024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19810329T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19961027T020000 RDATE:19961027T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:(DST) DTSTART:19970330T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:(STD) DTSTART:19971026T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD END:VTIMEZONE BEGIN:VEVENT CREATED:20241023T131035Z LAST-MODIFIED:20241023T131141Z DTSTAMP:20241023T131141Z UID:b9a23b47-f109-4e7a-908c-75e925b27def SUMMARY:event with alarms DTSTART;TZID=Europe/London:20241023T150000 DTEND;TZID=Europe/London:20241023T160000 TRANSP:OPAQUE X-MOZ-GENERATION:2 BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT15M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT45M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/alarm_thunderbird_snoozed_until_1457.ics000066400000000000000000000336751501302773300311130ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:Europe/London X-TZINFO:Europe/London[2024a] BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:-000115 TZNAME:Europe/London(STD) DTSTART:18471201T000000 RDATE:18471201T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19160521T020000 RDATE:19160521T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19161001T030000 RDATE:19161001T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19170408T020000 RDATE:19170408T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19170917T030000 RDATE:19170917T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19180324T020000 RDATE:19180324T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19180930T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19190330T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19201025T030000 RDATE:19201025T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19210403T020000 RDATE:19210403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19211003T030000 RDATE:19211003T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19220326T020000 RDATE:19220326T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19221008T030000 RDATE:19221008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19230422T020000 RDATE:19230422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19240413T020000 RDATE:19240413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19230916T030000 RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19250419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19270410T020000 RDATE:19270410T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19280422T020000 RDATE:19280422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19290421T020000 RDATE:19290421T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19300413T020000 RDATE:19300413T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19310419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19251004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19330409T020000 RDATE:19330409T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19331008T030000 RDATE:19331008T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19340422T020000 RDATE:19340422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19350414T020000 RDATE:19350414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19360419T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19380410T020000 RDATE:19380410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19341007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19390416T020000 RDATE:19390416T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19400225T020000 RDATE:19400225T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19410504T020000 RDATE:19410504T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19410810T030000 RDATE:19410810T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19420405T020000 RDATE:19420405T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19420809T030000 RDATE:19420809T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19430404T020000 RDATE:19430404T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19430815T030000 RDATE:19430815T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19440402T020000 RDATE:19440402T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19440917T030000 RDATE:19440917T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19450402T020000 RDATE:19450402T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19391119T030000 RDATE:19391119T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19450715T030000 RDATE:19450715T030000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19460414T020000 RDATE:19460414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19470316T020000 RDATE:19470316T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+020000 TZOFFSETFROM:+010000 TZNAME:Europe/London(DST) DTSTART:19470413T020000 RDATE:19470413T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19451007T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+020000 TZNAME:Europe/London(DST) DTSTART:19470810T030000 RDATE:19470810T030000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19471102T030000 RDATE:19471102T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19480314T020000 RDATE:19480314T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19490403T020000 RDATE:19490403T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19481031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19501022T030000 RDATE:19501022T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19511021T030000 RDATE:19511021T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19521026T030000 RDATE:19521026T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19500416T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19540411T020000 RDATE:19540411T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19550417T020000 RDATE:19550417T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19560422T020000 RDATE:19560422T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19570414T020000 RDATE:19570414T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19580420T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19600410T020000 RDATE:19600410T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19531004T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19610326T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000 END:DAYLIGHT BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19640322T020000 RDATE:19640322T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19611029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19651024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19650321T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19671029T030000 RDATE:19671029T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+010000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19681027T000000 RDATE:19681027T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19680218T020000 RDATE:19680218T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19711031T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19761024T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19720319T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19781029T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19811025T020000 RDATE:19811025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19821024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19841028T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19881023T020000 RDATE:19881023T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19891029T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000 END:STANDARD BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19931024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:Europe/London(DST) DTSTART:19810329T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:Europe/London(STD) DTSTART:19961027T020000 RDATE:19961027T020000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETTO:+010000 TZOFFSETFROM:+000000 TZNAME:(DST) DTSTART:19970330T010000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETTO:+000000 TZOFFSETFROM:+010000 TZNAME:(STD) DTSTART:19971026T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD END:VTIMEZONE BEGIN:VEVENT CREATED:20241023T131035Z LAST-MODIFIED:20241023T135202Z DTSTAMP:20241023T135202Z UID:b9a23b47-f109-4e7a-908c-75e925b27def SUMMARY:event with alarms X-MOZ-LASTACK:20241023T135202Z DTSTART;TZID=Europe/London:20241023T150000 DTEND;TZID=Europe/London:20241023T160000 TRANSP:OPAQUE X-MOZ-GENERATION:4 X-MOZ-SNOOZE-TIME:20241023T135702Z BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT15M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM BEGIN:VALARM ACTION:DISPLAY TRIGGER:-PT45M DESCRIPTION:Mozilla Standardbeschreibung END:VALARM END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/america_new_york.ics000066400000000000000000000025341501302773300252750ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VTIMEZONE TZID:custom_America/New_York LAST-MODIFIED:20050809T050000Z BEGIN:DAYLIGHT DTSTART:19670430T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19730429T070000Z TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:STANDARD DTSTART:19671029T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=20061029T060000Z TZOFFSETFROM:-0400 TZOFFSETTO:-0500 TZNAME:EST END:STANDARD BEGIN:DAYLIGHT DTSTART:19740106T020000 RDATE:19750223T020000 TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19760425T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19860427T070000Z TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19870405T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU;UNTIL=20060402T070000Z TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU TZOFFSETFROM:-0400 TZOFFSETTO:-0500 TZNAME:EST END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:noend123 DTSTART;TZID=custom_America/New_York;VALUE=DATE-TIME:20140829T080000 DTEND;TZID=custom_America/New_York;VALUE=DATE-TIME:20140829T100000 SUMMARY:an event with a custom tz name END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/america_new_york_forward_reference.ics000066400000000000000000000026241501302773300310370ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VEVENT UID:noend123 DTSTART;TZID=custom_America/New_York_Forward_reference;VALUE=DATE-TIME:20140829T080000 DTSTART;TZID=custom_America/New_York_Forward_reference;VALUE=DATE-TIME:20140829T100000 SUMMARY:an event with a custom tz name END:VEVENT BEGIN:VTIMEZONE TZID:custom_America/New_York_Forward_reference LAST-MODIFIED:20050809T050000Z BEGIN:DAYLIGHT DTSTART:19670430T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19730429T070000Z TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:STANDARD DTSTART:19671029T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=20061029T060000Z TZOFFSETFROM:-0400 TZOFFSETTO:-0500 TZNAME:EST END:STANDARD BEGIN:DAYLIGHT DTSTART:19740106T020000 RDATE:19750223T020000 TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19760425T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19860427T070000Z TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19870405T020000 RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU;UNTIL=20060402T070000Z TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:EDT END:DAYLIGHT BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU TZOFFSETFROM:-0400 TZOFFSETTO:-0500 TZNAME:EST END:STANDARD END:VTIMEZONE END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/big_bad_calendar.ics000066400000000000000000000007601501302773300251560ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT icalendar-6.3.1/src/icalendar/tests/calendars/bom_calendar.ics000066400000000000000000000000431501302773300243560ustar00rootroot00000000000000BEGIN:VCALENDAR END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/broken_ical.ics000066400000000000000000000002201501302773300242150ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VEVENT SUMMARY:An Event with too many semicolons DTSTART;;VALUE=DATE-TIME:20140409T093000 UID:abc END:VEVENT END:VCALENDARicalendar-6.3.1/src/icalendar/tests/calendars/calendar_with_unicode.ics000066400000000000000000000003061501302773300262640ustar00rootroot00000000000000BEGIN: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 END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/created_calendar_with_unicode_fields.ics000066400000000000000000000010631501302773300313020ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Plönë.org//NONSGML plone.app.event//EN X-WR-CALDESC:test non ascii: äöü ÄÖÜ € X-WR-CALNAME:äöü ÄÖÜ € X-WR-RELCALID:12345 BEGIN:VEVENT SUMMARY:Non-ASCII Test: ÄÖÜ äöü € DTSTART:20101010T100000Z DTEND:20101010T120000Z UID:123456 CREATED:20101010T000000Z DESCRIPTION:icalendar should be able to de/serialize non-ascii. LOCATION:Tribstrül END:VEVENT BEGIN:VEVENT SUMMARY:åäö DTSTART:20101010T000000Z END:VEVENT BEGIN:VEVENT DESCRIPTION:äöüßÄÖÜ END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/example.ics000066400000000000000000000015071501302773300234110ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:collective/icalendar CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:Holidays X-WR-TIMEZONE:Etc/GMT BEGIN:VEVENT SUMMARY:New Year's Day DTSTART:20220101 DTEND:20220101 DESCRIPTION:Happy New Year! UID:636a0cc1dbd5a1667894465@icalendar DTSTAMP:20221108T080105Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Orthodox Christmas DTSTART:20220107 DTEND:20220107 LOCATION:Russia DESCRIPTION:It is Christmas again! UID:636a0cc1dbfd91667894465@icalendar STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:International Women's Day DTSTART:20220308 DTEND:20220308 DESCRIPTION:May the feminine be honoured! UID:636a0cc1dc0f11667894465@icalendar STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT END:VCALENDARicalendar-6.3.1/src/icalendar/tests/calendars/issue_104_broken_calendar.ics000066400000000000000000000003531501302773300266610ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 METHOD:PUBLISH BEGIN:VEVENT DTSTART:20140401T000000Z DTEND:20140401T010000Z DTSTAMP:20140401T000000Z SUMMARY:Broken Eevnt CLASS:PUBLIC STATUS:CONFIRMED TRANSP:OPAQUE END:VEVENT X END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_156_RDATE_with_PERIOD_TZID_khal.ics000066400000000000000000000022731501302773300303470ustar00rootroot00000000000000BEGIN:VCALENDAR X-SOURCE-URL:https://github.com/pimutils/khal/issues/152#issuecomment-387410353 VERSION:2.0 PRODID:-//PIMUTILS.ORG//NONSGML khal / icalendar //EN BEGIN:VEVENT SUMMARY:Event DTSTART;TZID=America/Chicago;VALUE=DATE-TIME:20180327T080000 DTEND;TZID=America/Chicago;VALUE=DATE-TIME:20180327T090000 DTSTAMP:20180323T200333Z RECURRENCE-ID;RANGE=THISANDFUTURE:20180327T130000Z SEQUENCE:10 RDATE;TZID="Central Standard Time";VALUE=PERIOD:20180327T080000/20180327T0 90000,20180403T080000/20180403T090000,20180410T080000/20180410T090000,2018 0417T080000/20180417T090000,20180424T080000/20180424T090000,20180501T08000 0/20180501T090000,20180508T080000/20180508T090000,20180515T080000/20180515 T090000,20180522T080000/20180522T090000,20180529T080000/20180529T090000,20 180605T080000/20180605T090000,20180612T080000/20180612T090000,20180619T080 000/20180619T090000,20180626T080000/20180626T090000,20180703T080000/201807 03T090000,20180710T080000/20180710T090000,20180717T080000/20180717T090000, 20180724T080000/20180724T090000,20180731T080000/20180731T090000 ATTENDEE;CN="XYZ";PARTSTAT=ACCEPTED;ROLE=CHAIR;RSVP= FALSE:mailto:xyz@xyz.com CLASS:PUBLIC END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_156_RDATE_with_PERIOD_TZID_khal_2.ics000066400000000000000000000041161501302773300305660ustar00rootroot00000000000000BEGIN:VCALENDAR X-SOURCE-URL:https://github.com/pimutils/khal/issues/152#issuecomment-933635248 VERSION:2.0 PRODID:-//PIMUTILS.ORG//NONSGML khal / icalendar //EN BEGIN:VTIMEZONE TZID:Western/Central Europe BEGIN:STANDARD DTSTART:19501029T020000 RRULE:FREQ=YEARLY;BYMINUTE=0;BYHOUR=2;BYDAY=-1SU;BYMONTH=10 TZOFFSETFROM:+0200 TZOFFSETTO:+0100 END:STANDARD BEGIN:DAYLIGHT DTSTART:19500326T020000 RRULE:FREQ=YEARLY;BYMINUTE=0;BYHOUR=2;BYDAY=-1SU;BYMONTH=3 TZOFFSETFROM:+0100 TZOFFSETTO:+0200 END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT SUMMARY:(omitted) DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000 DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000 DTSTAMP:20211004T150245Z UID:BF5109494E67AAE20025875100566D31-Lotus_Notes_Generated RECURRENCE-ID;RANGE=THISANDFUTURE:20211101T150000Z SEQUENCE:0 RDATE;TZID="Western/Central Europe";VALUE=PERIOD:20211101T160000/20211101T 163000,20211206T160000/20211206T163000,20220103T160000/20220103T163000,202 20207T160000/20220207T163000 ATTENDEE;CN="(omitted)";PARTSTAT=ACCEPTED;ROLE=CHAIR;RSVP=FAL SE:mailto:omitted@example.com CLASS:PUBLIC TRANSP:OPAQUE X-LOTUS-APPTTYPE:3 X-LOTUS-AUDIOVIDEOFLAGS:0 X-LOTUS-BROADCAST:FALSE X-LOTUS-CHANGE-INST-DATES:20211101T150000Z\,20211206T150000Z\,20220103T150 000Z\,20220207T150000Z X-LOTUS-CHILD-UID:567EFBAF6CBD07FC0025875100566D3B X-LOTUS-INITIAL-RDATES:20211101T150000Z\,20211206T150000Z\,20220103T150000 Z\,20220207T150000Z X-LOTUS-LASTALL-RDATES;TZID="Western/Central Europe":20211101T160000\,2021 1206T160000\,20220103T160000\,20220207T160000 X-LOTUS-NOTESVERSION:2 X-LOTUS-NOTICETYPE:I X-LOTUS-RECURID;RANGE=THISANDFUTURE:20211101T150000Z X-LOTUS-UPDATE-SEQ:2 X-LOTUS-UPDATE-WISL:$W:1\;$O:1\;$M:1\;RequiredAttendees:1\;INetRequiredNam es:1\;AltRequiredNames:1\;StorageRequiredNames:1\;OptionalAttendees:1\;INe tOptionalNames:1\;AltOptionalNames:1\;StorageOptionalNames:1\;ApptUNIDURL: 1\;STUnyteConferenceURL:1\;STUnyteConferenceID:1\;SametimeType:1\;WhiteBoa rdContent:1\;STRoomName:1\;$S:2\;$B:2\;$L:2\;$E:2\;$R:2 END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_165_missing_event.ics000066400000000000000000000014241501302773300264310ustar00rootroot00000000000000BEGIN:VCALENDAR METHOD:REQUEST PRODID:Microsoft CDO for Microsoft Exchange VERSION:2.0 BEGIN:VTIMEZONE TZID:GMT +0100 (Standard) / GMT +0200 (Daylight) BEGIN:STANDARD DTSTART:16010101T030000 TZOFFSETFROM:+0200 TZOFFSETTO:+0100 RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU END:STANDARD BEGIN:DAYLIGHT DTSTART:16010101T020000 TZOFFSETFROM:+0100 TZOFFSETTO:+0200 RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT DTSTAMP:20150703T071009Z DTSTART;TZID="GMT +0100 (Standard) / GMT +0200 (Daylight)":20150703T100000 SUMMARY:Sprint 25 Daily Standup DTEND;TZID="GMT +0100 (Standard) / GMT +0200 (Daylight)":20150703T103000 RRULE:FREQ=DAILY;UNTIL=20150722T080000Z;INTERVAL=1;BYDAY=MO, TU, WE, TH, FR ;WKST=SU END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_168_expected_output.ics000066400000000000000000000001651501302773300270040ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VEVENT DTSTART:20150905T090000Z DTEND:20150905T100000Z UID:123 END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_168_input.ics000066400000000000000000000002271501302773300247210ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VEVENT DTSTART:20150905T090000Z DTEND:20150905T100000Z UID:123 X-APPLE-RADIUS=49.91307046514149 END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_178_component_with_invalid_name_represented.ics000066400000000000000000000000321501302773300337200ustar00rootroot00000000000000BEGIN:MYCOMP END:MYCOMP icalendar-6.3.1/src/icalendar/tests/calendars/issue_178_custom_component_contains_other.ics000066400000000000000000000001601501302773300322520ustar00rootroot00000000000000BEGIN:MYCOMPTOO DTSTAMP:20150121T080000 BEGIN:VEVENT DTSTART:20150122 UID:12345 END:VEVENT END:MYCOMPTOO icalendar-6.3.1/src/icalendar/tests/calendars/issue_178_custom_component_inside_other.ics000066400000000000000000000001061501302773300317070ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:UNKNOWN UID:1234 END:UNKNOWN END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_218_bad_tzid.ics000066400000000000000000000006761501302773300253460ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:-//TEST//TEST//EN BEGIN:VTIMEZONE TZID:UTC+11 BEGIN:STANDARD DTSTART;VALUE=DATE:20170101 TZNAME:UTC+11 TZOFFSETFROM:+1100 TZOFFSETTO:+1100 END:STANDARD END:VTIMEZONE BEGIN:VEVENT DESCRIPTION:TESTING DTEND;TZID=UTC+11:20170228T233000 DTSTAMP:20170227T064302Z DTSTART;TZID=UTC+11:20170228T230000 RESOURCES:Court 4 SEQUENCE:0 STATUS:Confirmed SUMMARY:TESTIN UID:1961094_636238800000000000 END:VEVENT END:VCALENDAR issue_237_fail_to_parse_timezone_with_non_ascii_tzid.ics000066400000000000000000000010651501302773300343310ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/calendarsBEGIN:VCALENDAR BEGIN:VTIMEZONE TZID:(UTC-03:00) Brasília BEGIN:STANDARD TZNAME:Brasília standard DTSTART:16010101T235959 TZOFFSETFROM:-0200 TZOFFSETTO:-0300 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=3SA;BYMONTH=2 END:STANDARD BEGIN:DAYLIGHT TZNAME:Brasília daylight DTSTART:16010101T235959 TZOFFSETFROM:-0300 TZOFFSETTO:-0200 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SA;BYMONTH=10 END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT DTSTART;TZID="(UTC-03:00) Brasília":20170511T133000 DTEND;TZID="(UTC-03:00) Brasília":20170511T140000 END:VEVENT END:VCALENDAR issue_27_multiple_periods_in_freebusy_multiple_freebusies.ics000066400000000000000000000013441501302773300355170ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/calendarsBEGIN:VCALENDAR VERSION:2.0 PRODID:-//davmail.sf.net/NONSGML DavMail Calendar V1.1//EN METHOD:REPLY BEGIN:VFREEBUSY DTSTAMP:20120131T123000Z ORGANIZER:MAILTO:organizer@domain.tld DTSTART:20120101T000000Z DTEND:20120201T000000Z UID:null ATTENDEE:MAILTO:attendee@domain.tld FREEBUSY;FBTYPE=BUSY:20120103T091500Z/20120103T101500Z FREEBUSY;FBTYPE=BUSY:20120113T130000Z/20120113T150000Z FREEBUSY;FBTYPE=BUSY:20120116T130000Z/20120116T150000Z FREEBUSY;FBTYPE=BUSY:20120117T091500Z/20120117T101500Z FREEBUSY;FBTYPE=BUSY:20120118T160000Z/20120118T163000Z FREEBUSY;FBTYPE=BUSY:20120124T083000Z/20120124T093000Z FREEBUSY;FBTYPE=BUSY:20120124T123000Z/20120124T143000Z FREEBUSY;FBTYPE=BUSY:20120131T091500Z/20120131T101500Z END:VFREEBUSY END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_27_multiple_periods_in_freebusy_one_freebusy.ics000066400000000000000000000011211501302773300342050ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:-//davmail.sf.net/NONSGML DavMail Calendar V1.1//EN METHOD:REPLY BEGIN:VFREEBUSY DTSTAMP:20120131T123000Z ORGANIZER:MAILTO:organizer@domain.tld DTSTART:20120101T000000Z DTEND:20120201T000000Z UID:null ATTENDEE:MAILTO:attendee@domain.tld FREEBUSY;FBTYPE=BUSY:20120103T091500Z/20120103T101500Z,20120113T130000Z/20120113T150000Z,20120116T130000Z/20120116T150000Z,20120117T091500Z/20120117T101500Z,20120118T160000Z/20120118T163000Z,20120124T083000Z/20120124T093000Z,20120124T123000Z/20120124T143000Z,20120131T091500Z/20120131T101500Z END:VFREEBUSY END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_322_expected_calendar.ics000066400000000000000000000002111501302773300271750ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VEVENT SUMMARY:Event with bare string as argument for categories CATEGORIES:Lecture END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_348_exception_parsing_value.ics000066400000000000000000000026421501302773300305020ustar00rootroot00000000000000BEGIN:VCALENDAR CALSCALE:GREGORIAN METHOD:PUBLISH VERSION:2.0 PRODID:-//Sixt//RAC//EN BEGIN:VFREEBUSY FREEBUSY;FBTYPE=FREE:20190624T063000Z/20190624T163000Z ORGANIZER;CN=Sixt SE X-ORGANIZER2;CN=Sixt SE;CN2=Test! UID:SIXT_9879691160 DTSTAMP:20190612T104813Z END:VFREEBUSY BEGIN:VEVENT CLASS:PUBLIC DESCRIPTION:\nVotre véhicule avec le numéro de réservation xxxxxxxxxxx sera à votre disposition le 24.06.2019 à 08:30 heures (heure locale). Retour prévu : Ferney-Voltaire AP de Genève Cointrin Sect(F), le 24.06.2019 à 18:30 heures.\nVous trouverez des informations relatives à la modification ou à l'annulation de votre réservation sous http://www.sixt.fr\n DTSTART:20190624T063000Z DTSTAMP:20190612T104813Z DTEND:20190624T163000Z LOCATION:Sixt Genève Aéroport secteur France, AP de Genève Cointrin Sect(F), 01210 Ferney-Voltaire, FR PRIORITY:5 TRANSP:TRANSPARENT SEQUENCE:0 STATUS:CONFIRMED SUMMARY:Sixt : détails de votre réservation UID:SIXT_9879691160 BEGIN:VALARM TRIGGER:-PT30M ACTION:DISPLAY DESCRIPTION:\nLe véhicule avec la n° de réservation xxxxxxxxxxx sera à votre disposition entre 24.06.2019 et 08:30 heures (heure locale) à l'agence Genève Aéroport secteur France. Retour prévu : Ferney-Voltaire AP de Genève Cointrin Sect(F), le 24.06.2019 à 18:30 heures.\nTrouver des informations de localisation et modifier / annuler votre réservation ici: http://www.sixt.fr\n END:VALARM END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_350.ics000066400000000000000000000025061501302773300234750ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Podio API//EN//view-exporter//52593453 METHOD:REQUEST X-WR-CALNAME:view52593586 X-WR-TIMEZONE:Europe/Berlin CALSCALE:GREGORIAN X-COMMENT-USAGE:This calendar does not contain recurring events! X-COMMENT-GENERATOR:PHP Version 8.0.16 BEGIN:VEVENT STATUS:CONFIRMED SEQUENCE:0 TRANSP:OPAQUE CLASS:PUBLIC UID:20055546456446 SUMMARY:Termin 4353 und"so" DESCRIPTION;ALTREP="data:text/html,%3Cbody%3E%3Cp%3EToller%20%3Cstron g%20class%3D%22text-bold%22%3ETermin%3C%2Fstrong%3E%20f%C3%BCr%3C%2Fp %3E%3Cblockquote%3E%3Cp%3Emal%20%3Cem%20class%3D%22text-italic%22%3Ez u%22gucken%22%3C%2Fem%3E%3C%2Fp%3E%3C%2Fblockquote%3E%3Cp%3E%3Cu%20cl ass%3D%22text-underline%22%3Eund%3C%2Fu%3E%20%3Cdel%3Eso%3C%2Fdel%3E% 3Cbr%2F%3E%3C%2Fp%3E%3C%2Fbody%3E":Toller Termin fürmal zu\"gucken\" und so X-ALT-DESC;FMTTYPE=text/html:

Toller Termin für

mal zu"gucken"

und so

URL:https://podio.com/xxxxxxyyyyyy/zpodio-testgelande/apps/calen dar/items/5 LOCATION:online DTSTART:20220222T183000Z DTEND:20220222T193000Z DTSTAMP:20220220T142821Z END:VEVENT END:VCALENDAR X-COMMENT:Cached from 2022-02-20 14:28:21 - new at most every 1800sec. icalendar-6.3.1/src/icalendar/tests/calendars/issue_466_convert_tzid_with_slash.ics000066400000000000000000000005051501302773300305210ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VEVENT UID:0cab49a0-1167-40f0-bfed-ecb4d117047d DTSTAMP:20221019T102950Z DTSTART;TZID=/Europe/Stockholm:20221021T200000 DTEND;TZID=/Europe/Stockholm:20221021T210000 SUMMARY:Just chatting DESCRIPTION:Just Chatting. CATEGORIES:Just Chatting RRULE:FREQ=WEEKLY;BYDAY=FR END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_466_respect_unique_timezone.ics000066400000000000000000000011731501302773300305310ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VTIMEZONE TZID:/Europe/CUSTOM 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 UID:0cab49a0-1167-40f0-bfed-ecb4d117047d DTSTAMP:20221019T102950Z DTSTART;TZID=/Europe/CUSTOM:20221021T200000 DTEND;TZID=/Europe/CUSTOM:20221021T210000 SUMMARY:Just chatting DESCRIPTION:Just Chatting. CATEGORIES:Just Chatting RRULE:FREQ=WEEKLY;BYDAY=FR END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_526_calendar_with_different_events.ics000066400000000000000000000010601501302773300317720ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:icalendar-2023 BEGIN:VEVENT UID:ical-jacadzaca-3 SUMMARY: Some very different event ':' DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000 DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000 DTSTAMP:20211004T150245Z END:VEVENT BEGIN:VEVENT UID:ical-jacadzaca-4 SUMMARY: Some very different other event DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T164000 DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T165000 DTSTAMP:20211004T150245Z END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_526_calendar_with_event_subset.ics000066400000000000000000000004311501302773300311470ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:icalendar-2023 BEGIN:VEVENT UID:1 SUMMARY: Some event ':' DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000 DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000 DTSTAMP:20211004T150245Z END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_526_calendar_with_events.ics000066400000000000000000000007641501302773300277560ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:icalendar-2023 BEGIN:VEVENT UID:1 SUMMARY: Some event ':' DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000 DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000 DTSTAMP:20211004T150245Z END:VEVENT BEGIN:VEVENT UID:2 SUMMARY: Some other event DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T164000 DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T165000 DTSTAMP:20211004T150245Z END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_526_calendar_with_shuffeled_events.ics000066400000000000000000000007641501302773300320030ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:icalendar-2023 BEGIN:VEVENT UID:2 SUMMARY: Some other event DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T164000 DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T165000 DTSTAMP:20211004T150245Z END:VEVENT BEGIN:VEVENT UID:1 SUMMARY: Some event ':' DTSTART;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T160000 DTEND;TZID="Western/Central Europe";VALUE=DATE-TIME:20211101T163000 DTSTAMP:20211004T150245Z END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_722_missing_VTIMEZONE_custom.ics000066400000000000000000000001571501302773300302630ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VEVENT DTSTART;TZID=CUSTOM_tzid;VALUE=DATE-TIME:20140829T080000 END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_722_missing_timezones.ics000066400000000000000000000012111501302773300273160ustar00rootroot00000000000000BEGIN:VCALENDAR DESCRIPTION:We leave the timezones out but we use common names so that they are added. BEGIN:VEVENT DTSTART;TZID=America/New_York;VALUE=DATE-TIME:20140829T080000 DTEND;TZID=America/Los_Angeles;VALUE=DATE-TIME:20140829T080000 RDATE;VALUE=PERIOD;TZID=Europe/Berlin:20240913T120000/PT2H SUMMARY:an event with a custom tz name END:VEVENT BEGIN:VEVENT RECURRENCE-ID;TZID=Europe/Moscow:20190309T020000 END:VEVENT BEGIN:VTODO DUE;TZID=Asia/Singapore;VALUE=DATE-TIME:20140829T080000 END:VTODO BEGIN:VEVENT RDATE;TZID=Mexico/General:20190309T020000 RDATE;TZID=America/Noronha:20190309T020000 DTSTAMP:19920901T130000Z END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/issue_722_timezone_transition_ambiguity.ics000066400000000000000000000020311501302773300317270ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VTIMEZONE TZID:MyTimezone BEGIN:STANDARD COMMENT:The timezone starts at 2024-01-01 with +12h offset TZOFFSETFROM:+1000 TZOFFSETTO:+1200 DTSTART:20240101T000000 TZNAME:winter END:STANDARD BEGIN:DAYLIGHT COMMENT:The timezone goes from +12h to +10h at 8 am TZOFFSETFROM:+1200 TZOFFSETTO:+1000 DTSTART:20240505T080000 TZNAME:summer END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT UID:0 SUMMARY:This event is clearly in winter DTSTART;TZID=MyTimezone;VALUE=DATE-TIME:20240303T080000 X-TZNAME:winter END:VEVENT BEGIN:VEVENT UID:1 SUMMARY:This event is clearly in summer X-TZNAME:summer DTSTART;TZID=MyTimezone;VALUE=DATE-TIME:20240803T080000 END:VEVENT BEGIN:VEVENT UID:2 SUMMARY:Transition is from 8am -> 6am, so 8:00:01 is summer X-TZNAME:summer DTSTART;TZID=MyTimezone;VALUE=DATE-TIME:20240505T080001 END:VEVENT BEGIN:VEVENT UID:3 SUMMARY:Transition is from 8am -> 6am, so 7:00:01 is winter RFC5545 does not allow us to be at the later TZ. X-TZNAME:winter DTSTART;TZID=MyTimezone;VALUE=DATE-TIME:20240505T070001 END:VEVENT END:VCALENDARicalendar-6.3.1/src/icalendar/tests/calendars/issue_798_freebusy.ics000066400000000000000000000005131501302773300254150ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VFREEBUSY FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:19970308T160000Z/PT8H30M END:VFREEBUSY BEGIN:VFREEBUSY FREEBUSY:19970308T160000Z/PT3H,19970308T200000Z/PT1H END:VFREEBUSY BEGIN:VFREEBUSY FREEBUSY;FBTYPE=FREE:19970308T160000Z/PT3H,19970308T200000Z/PT1H ,19970308T230000Z/19970309T000000Z END:VFREEBUSY END:VCALENDARicalendar-6.3.1/src/icalendar/tests/calendars/issue_798_related_to.ics000066400000000000000000000003141501302773300257120ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VEVENT RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com END:VEVENT BEGIN:VEVENT RELATED-TO;RELTYPE=SIBLING:19960401-080045-4000F192713@example.com END:VEVENT END:VCALENDARicalendar-6.3.1/src/icalendar/tests/calendars/issue_836_do_not_quote_tzid.ics000066400000000000000000000012261501302773300273150ustar00rootroot00000000000000BEGIN:VCALENDAR METHOD:PUBLISH PRODID:Microsoft Exchange Server 2010 VERSION:2.0 BEGIN:VTIMEZONE TZID:Eastern Standard Time BEGIN:STANDARD DTSTART:16010101T020000 TZOFFSETFROM:-0400 TZOFFSETTO:-0500 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11 END:STANDARD BEGIN:DAYLIGHT DTSTART:16010101T020000 TZOFFSETFROM:-0500 TZOFFSETTO:-0400 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3 END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT UID:minimal-demo-event-est-20241028@example.com SUMMARY:Anonymous Test Event for TZID DTSTART;TZID=Eastern Standard Time:20241028T170000 DTEND;TZID=Eastern Standard Time:20241028T180000 DTSTAMP:20250514T023916Z END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/multiple_calendar_components.ics000066400000000000000000000016641501302773300277130ustar00rootroot00000000000000BEGIN: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-6.3.1/src/icalendar/tests/calendars/pacific_fiji.ics000066400000000000000000000023061501302773300243530ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//tzurl.org//NONSGML Olson 2014g//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:custom_Pacific/Fiji TZURL:http://tzurl.org/zoneinfo/Pacific/Fiji X-LIC-LOCATION:Pacific/Fiji BEGIN:DAYLIGHT TZOFFSETFROM:+1200 TZOFFSETTO:+1300 DTSTART:20101024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYMONTHDAY=21,22,23,24,25,26,27;BYDAY=SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+1300 TZOFFSETTO:+1200 DTSTART:20140119T020000 RRULE:FREQ=YEARLY;BYMONTH=1;BYMONTHDAY=18,19,20,21,22,23,24;BYDAY=SU END:STANDARD BEGIN:STANDARD TZOFFSETFROM:+115544 TZOFFSETTO:+1200 DTSTART:19151026T000000 RDATE:19151026T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:+1200 TZOFFSETTO:+1300 DTSTART:19981101T020000 RDATE:19981101T020000 RDATE:19991107T020000 RDATE:20091129T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+1300 TZOFFSETTO:+1200 DTSTART:19990228T030000 RDATE:19990228T030000 RDATE:20000227T030000 RDATE:20100328T030000 RDATE:20110306T030000 RDATE:20120122T030000 RDATE:20130120T030000 END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:noend123 DTSTART;TZID=custom_Pacific/Fiji;VALUE=DATE-TIME:20140829T080000 DTSTART;TZID=custom_Pacific/Fiji;VALUE=DATE-TIME:20140829T100000 SUMMARY:an event with a custom tz name END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/parsing_error.ics000066400000000000000000000007521501302773300246330ustar00rootroot00000000000000BEGIN: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:Wrong 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 icalendar-6.3.1/src/icalendar/tests/calendars/parsing_error_in_UTC_offset.ics000066400000000000000000000002751501302773300274020ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VTIMEZONE TZID:Europe/Prague BEGIN:STANDARD DTSTART:18500101T000000 TZNAME:PMT TZOFFSETFROM:+5744 TZOFFSETTO:+5744 END:STANDARD END:VTIMEZONE END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/period_with_timezone.ics000066400000000000000000000013501501302773300262010ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 X-WR-CALNAME;VALUE=TEXT:Test RDATE BEGIN:VTIMEZONE TZID:America/Vancouver BEGIN:STANDARD DTSTART:20221106T020000 TZOFFSETFROM:-0700 TZOFFSETTO:-0800 RDATE:20231105T020000 TZNAME:PST END:STANDARD BEGIN:DAYLIGHT DTSTART:20230312T020000 TZOFFSETFROM:-0800 TZOFFSETTO:-0700 RDATE:20240310T020000 TZNAME:PDT END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT UID:1 DESCRIPTION:Test RDATE DTSTART;TZID=America/Vancouver:20230920T120000 DTEND;TZID=America/Vancouver:20230920T140000 EXDATE;TZID=America/Vancouver:20231220T120000 RDATE;VALUE=PERIOD;TZID=America/Vancouver:20231213T120000/20231213T150000 RRULE:FREQ=MONTHLY;COUNT=9;INTERVAL=1;BYDAY=+3WE;BYMONTH=1,2,3,4,5,9,10,11, 12;WKST=MO SUMMARY:Test RDATE END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/pr_480_summary_with_colon.ics000066400000000000000000000002451501302773300267720ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VEVENT SUMMARY:Example calendar with a ': ' in the summary END:VEVENT BEGIN:VEVENT SUMMARY:Another event with a ': ' in the summary END:VEVENT icalendar-6.3.1/src/icalendar/tests/calendars/property_params.ics000066400000000000000000000012241501302773300252010ustar00rootroot00000000000000BEGIN: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 icalendar-6.3.1/src/icalendar/tests/calendars/rfc_5545_RDATE_example.ics000066400000000000000000000013641501302773300256450ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VEVENT UID:0 RDATE:19970714T123000 END:VEVENT BEGIN:VEVENT UID:1 RDATE:19970714T123000Z END:VEVENT BEGIN:VEVENT UID:2 RDATE;TZID=America/New_York:19970714T083000 END:VEVENT BEGIN:VEVENT UID:3 RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z, 19960404T010000Z/PT3H END:VEVENT BEGIN:VEVENT UID:4 RDATE;VALUE=DATE:19970101,19970120,19970217,19970421, 19970526,19970704,19970901,19971014,19971128,19971129,19971225 END:VEVENT BEGIN:VEVENT UID:5 RDATE;VALUE=DATE:19970101,19970120,19970217,19970421, 19970526,19970704,19970901,19971014,19971128,19971129,19971225 RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z, 19960404T010000Z/PT3H RDATE:19970714T123000Z RDATE;TZID=America/New_York:19970714T083000 END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/rfc_6868.ics000066400000000000000000000002511501302773300232160ustar00rootroot00000000000000BEGIN:VCALENDAR X-PARAM;NEWLINE=^n;ALL=^^^'^n;UNKNOWN=^a^ ^asd:asd BEGIN:VEVENT ATTENDEE;CN=George Herman ^'Babe^' Ruth:mailto:babe@example.com END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/rfc_7529.ics000066400000000000000000000011731501302773300232150ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID://RESEARCH IN MOTION//BIS 3.0 METHOD:REQUEST BEGIN:VEVENT UID:4.3.1 DTSTART;VALUE=DATE:20130210 RRULE:RSCALE=CHINESE;FREQ=YEARLY SUMMARY:Chinese New Year END:VEVENT BEGIN:VEVENT UID:4.3.2 DTSTART;VALUE=DATE:20130906 RRULE:RSCALE=ETHIOPIC;FREQ=MONTHLY;BYMONTH=13 SUMMARY:First day of 13th month END:VEVENT BEGIN:VEVENT UID:4.3.3 DTSTART;VALUE=DATE:20140208 RRULE:RSCALE=HEBREW;FREQ=YEARLY;BYMONTH=5L;BYMONTHDAY=8;SKIP=FORWARD SUMMARY:Anniversary END:VEVENT BEGIN:VEVENT UID:4.3.4 DTSTART;VALUE=DATE:20120229 RRULE:RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD SUMMARY:Anniversary END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/small_bad_calendar.ics000066400000000000000000000000501501302773300255150ustar00rootroot00000000000000BEGIN:VCALENDAR BEGIN:VEVENT END:VEVENT icalendar-6.3.1/src/icalendar/tests/calendars/time.ics000066400000000000000000000000731501302773300227110ustar00rootroot00000000000000BEGIN:VCALENDAR X-SOMETIME;VALUE=TIME:172010 END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/timezone_rdate.ics000066400000000000000000000021251501302773300247640ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 BEGIN:VTIMEZONE TZID:posix/Europe/Vaduz BEGIN:STANDARD TZNAME:CET TZOFFSETFROM:+002946 TZOFFSETTO:+0100 DTSTART:19011213T211538 RDATE;VALUE=DATE-TIME:19011213T211538 END:STANDARD BEGIN:DAYLIGHT TZNAME:CEST TZOFFSETFROM:+0100 TZOFFSETTO:+0200 DTSTART:19810329T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 END:DAYLIGHT BEGIN:DAYLIGHT TZNAME:CEST TZOFFSETFROM:+0100 TZOFFSETTO:+0200 DTSTART:19410505T010000 RDATE;VALUE=DATE-TIME:19410505T010000 RDATE;VALUE=DATE-TIME:19420504T010000 END:DAYLIGHT BEGIN:STANDARD TZNAME:CET TZOFFSETFROM:+0200 TZOFFSETTO:+0100 DTSTART:19810927T030000 RRULE:FREQ=YEARLY;COUNT=15;BYDAY=-1SU;BYMONTH=9 END:STANDARD BEGIN:STANDARD TZNAME:CET TZOFFSETFROM:+0200 TZOFFSETTO:+0100 DTSTART:19961027T030000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 END:STANDARD BEGIN:STANDARD TZNAME:CET TZOFFSETFROM:+0200 TZOFFSETTO:+0100 DTSTART:19411006T020000 RDATE;VALUE=DATE-TIME:19411006T020000 RDATE;VALUE=DATE-TIME:19421005T020000 END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:123 DTSTART;TZID=posix/Europe/Vaduz:20120213T100000 SUMMARY=testevent END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/timezone_same_start.ics000066400000000000000000000012551501302773300260320ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:Microsoft Exchange Server 2010 METHOD:REQUEST BEGIN:VTIMEZONE TZID:Pacific Standard Time BEGIN:STANDARD DTSTART:16010101T020000 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11 TZOFFSETFROM:-0700 TZOFFSETTO:-0800 END:STANDARD BEGIN:DAYLIGHT DTSTART:16010101T020000 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3 TZOFFSETFROM:-0800 TZOFFSETTO:-0700 END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT SUMMARY;LANGUAGE=en-US:Test 4 DTSTART;TZID="Pacific Standard Time":20170224T120000 DTEND;TZID="Pacific Standard Time":20170224T123000 DTSTAMP:20170224T180431Z UID:040000008200E00074C5B7101A82E0080000000090E19664858ED20100000000000000 END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/calendars/timezone_same_start_and_offset.ics000066400000000000000000000007331501302773300302220ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:Microsoft Exchange Server 2010 BEGIN:VTIMEZONE TZID:Tokyo Standard Time BEGIN:STANDARD DTSTART:16010101T000000 TZOFFSETFROM:+0900 TZOFFSETTO:+0900 END:STANDARD BEGIN:DAYLIGHT DTSTART:16010101T000000 TZOFFSETFROM:+0900 TZOFFSETTO:+0900 END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT DTSTART;TZID="Tokyo Standard Time":20170224T120000 DTEND;TZID="Tokyo Standard Time":20170224T123000 UID:blafoobar SUMMARY:this is an event END:VEVENT END:VCALENDARD icalendar-6.3.1/src/icalendar/tests/calendars/timezoned.ics000066400000000000000000000014541501302773300237550ustar00rootroot00000000000000BEGIN: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-6.3.1/src/icalendar/tests/calendars/x_location.ics000066400000000000000000000025721501302773300241200ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:ITC X-WR-TIMEZONE:Europe/Zurich X-WR-CALDESC:ITC Bookings BEGIN:VTIMEZONE TZID:Europe/Zurich X-LIC-LOCATION:Europe/Zurich 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/Zurich:20161028T140000 DTEND;TZID=Europe/Zurich:20161028T143000 RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR DTSTAMP:20161031T192828Z UID:BFE33ADD-5553-48B5-B5A5-F9DA5CA4C393 CREATED:20161029T121229Z DESCRIPTION:Some Description LAST-MODIFIED:20161029T121229Z LOCATION:Roadstar 16\n12764 Happyville\nDenmark SEQUENCE:0 STATUS:CONFIRMED SUMMARY:Daily Sync TRANSP:OPAQUE X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS="Röadstar 16\n12764 Happyvi lle\nDenmark";X-APPLE-MAPKIT-HANDLE=CAESARoSCWYTYFhHQBEGfw4hQCIBDQoHRGVubW FyaxJES0hhcHB5dmlsbGUqSGFwcHl2aWxsZTIHSGFwcHl2aWxsZToEMTI3NjRCDQpSb2Fkc3Rh cloCMTZiUm9hZHN0YXIgMTYBEU1vcmRvcgENCk1vcmRvcioSUm9hZHN0YXIgMTYyUm9hZHN0YX IgMTYxMjc2NCBIYXBweXZpbGxlMgdEZW5tYXJrOThA=;X-APPLE-RADIUS=49.913058665846 98;X-APPLE-REFERENCEFRAME=1;X-TITLE=:geo:52.382762,7.528319 END:VEVENT END:VCALENDAR icalendar-6.3.1/src/icalendar/tests/conftest.py000066400000000000000000000222131501302773300215160ustar00rootroot00000000000000try: from backports import zoneinfo # type: ignore # noqa: PGH003 except ImportError: import zoneinfo from typing import Generator import pytest import icalendar from . import timezone_ids try: import pytz except ImportError: pytz = None import itertools import sys import uuid from pathlib import Path from dateutil import tz from icalendar.cal import Calendar, Component from icalendar.timezone import TZP from icalendar.timezone import tzp as _tzp HAS_PYTZ = pytz is not None if HAS_PYTZ: PYTZ_UTC = [ pytz.utc, pytz.timezone("UTC"), ] PYTZ_IN_TIMEZONE = [ lambda dt, tzname: pytz.timezone(tzname).localize(dt), ] PYTZ_TZP = ["pytz"] else: PYTZ_UTC = [] PYTZ_IN_TIMEZONE = [] PYTZ_TZP = [] class DataSource: """A collection of parsed ICS elements (e.g calendars, timezones, events)""" def __init__(self, data_source_folder: Path, parser): self._parser = parser self._data_source_folder = data_source_folder def keys(self): """Return all the files that could be used.""" return [ p.stem for p in self._data_source_folder.iterdir() if p.suffix.lower() == ".ics" ] def __getitem__(self, attribute): """Parse a file and return the result stored in the attribute.""" if attribute.endswith(".ics"): source_file = attribute attribute = attribute[:-4] else: source_file = attribute + ".ics" source_path = self._data_source_folder / source_file if not source_path.is_file(): raise AttributeError(f"{source_path} does not exist.") with source_path.open("rb") as f: raw_ics = f.read() source = self._parser(raw_ics) if not isinstance(source, list): source.raw_ics = raw_ics source.source_file = source_file self.__dict__[attribute] = source return source def __contains__(self, key): """key in self.keys()""" if key.endswith(".ics"): key = key[:-4] return key in self.keys() def __getattr__(self, key): return self[key] def __repr__(self): return repr(self.__dict__) @property def multiple(self): """Return a list of all components parsed.""" return self.__class__( self._data_source_folder, lambda data: self._parser(data, multiple=True) ) HERE = Path(__file__).parent CALENDARS_FOLDER = HERE / "calendars" TIMEZONES_FOLDER = HERE / "timezones" EVENTS_FOLDER = HERE / "events" ALARMS_FOLDER = HERE / "alarms" @pytest.fixture(scope="module") def calendars(tzp): return DataSource(CALENDARS_FOLDER, icalendar.Calendar.from_ical) @pytest.fixture(scope="module") def timezones(tzp): return DataSource(TIMEZONES_FOLDER, icalendar.Timezone.from_ical) @pytest.fixture(scope="module") def events(tzp): return DataSource(EVENTS_FOLDER, icalendar.Event.from_ical) @pytest.fixture(scope="module") def alarms(tzp): return DataSource(ALARMS_FOLDER, icalendar.Alarm.from_ical) @pytest.fixture(params=PYTZ_UTC + [zoneinfo.ZoneInfo("UTC"), tz.UTC, tz.gettz("UTC")]) def utc(request, tzp): return request.param @pytest.fixture( params=PYTZ_IN_TIMEZONE + [ lambda dt, tzname: dt.replace(tzinfo=tz.gettz(tzname)), lambda dt, tzname: dt.replace(tzinfo=zoneinfo.ZoneInfo(tzname)), ] ) def in_timezone(request, tzp): return request.param # exclude broken calendars here ICS_FILES_EXCLUDE = ( "big_bad_calendar.ics", "issue_104_broken_calendar.ics", "small_bad_calendar.ics", "multiple_calendar_components.ics", "pr_480_summary_with_colon.ics", "parsing_error_in_UTC_offset.ics", "parsing_error.ics", ) ICS_FILES = [ file.name for file in itertools.chain( CALENDARS_FOLDER.iterdir(), TIMEZONES_FOLDER.iterdir(), EVENTS_FOLDER.iterdir() ) if file.name not in ICS_FILES_EXCLUDE ] @pytest.fixture(params=ICS_FILES) def ics_file(tzp, calendars, timezones, events, request): """An example ICS file.""" ics_file = request.param print("example file:", ics_file) for data in calendars, timezones, events: if ics_file in data: return data[ics_file] raise ValueError(f"Could not find file {ics_file}.") FUZZ_V1 = [key for key in CALENDARS_FOLDER.iterdir() if "fuzz-testcase" in str(key)] @pytest.fixture(params=FUZZ_V1) def fuzz_v1_calendar(request): """Clusterfuzz calendars.""" return request.param @pytest.fixture def x_sometime(): """Map x_sometime to time""" icalendar.cal.types_factory.types_map["X-SOMETIME"] = "time" yield icalendar.cal.types_factory.types_map.pop("X-SOMETIME") @pytest.fixture def factory(): """Return a new component factory.""" return icalendar.ComponentFactory() @pytest.fixture def vUTCOffset_ignore_exceptions(): icalendar.vUTCOffset.ignore_exceptions = True yield icalendar.vUTCOffset.ignore_exceptions = False @pytest.fixture def event_component(tzp): """Return an event component.""" c = Component() c.name = "VEVENT" return c @pytest.fixture def c(tzp): """Return an empty component.""" c = Component() return c comp = c @pytest.fixture def calendar_component(tzp): """Return an empty component.""" c = Component() c.name = "VCALENDAR" return c @pytest.fixture def filled_event_component(c, calendar_component): """Return an event with some values and add it to calendar_component.""" e = Component(summary="A brief history of time") e.name = "VEVENT" e.add("dtend", "20000102T000000", encode=0) e.add("dtstart", "20000101T000000", encode=0) calendar_component.add_component(e) return e @pytest.fixture() def calendar_with_resources(tzp): c = Calendar() c["resources"] = 'Chair, Table, "Room: 42"' return c @pytest.fixture(scope="module") def tzp(tzp_name) -> Generator[TZP, None, None]: """The timezone provider.""" _tzp.use(tzp_name) yield _tzp _tzp.use_default() @pytest.fixture(params=PYTZ_TZP + ["zoneinfo"]) def other_tzp(request, tzp): """This is annother timezone provider. The purpose here is to cross test: pytz <-> zoneinfo. tzp as parameter makes sure we test the cross product. """ return TZP(request.param) @pytest.fixture def pytz_only(tzp, tzp_name) -> str: """Skip tests that are not running under pytz.""" assert tzp.uses_pytz() return tzp_name @pytest.fixture def zoneinfo_only(tzp, request, tzp_name) -> str: """Skip tests that are not running under zoneinfo.""" assert tzp.uses_zoneinfo() return tzp_name @pytest.fixture def no_pytz(tzp_name) -> str: """Do not run tests with pytz.""" assert tzp_name != "pytz" return tzp_name @pytest.fixture def no_zoneinfo(tzp_name) -> str: """Do not run tests with zoneinfo.""" assert tzp_name != "zoneinfo" return tzp_name def pytest_generate_tests(metafunc): """Parametrize without skipping: tzp_name will be parametrized according to the use of - pytz_only - zoneinfo_only - no_pytz - no_zoneinfo See https://docs.pytest.org/en/6.2.x/example/parametrize.html#deferring-the-setup-of-parametrized-resources """ if "tzp_name" in metafunc.fixturenames: tzp_names = PYTZ_TZP + ["zoneinfo"] if "zoneinfo_only" in metafunc.fixturenames: tzp_names = ["zoneinfo"] if "pytz_only" in metafunc.fixturenames: tzp_names = PYTZ_TZP assert not ( "zoneinfo_only" in metafunc.fixturenames and "pytz_only" in metafunc.fixturenames ), "Use pytz_only or zoneinfo_only but not both!" for name in ["pytz", "zoneinfo"]: if f"no_{name}" in metafunc.fixturenames and name in tzp_names: tzp_names.remove(name) metafunc.parametrize("tzp_name", tzp_names, scope="module") class DoctestZoneInfo(zoneinfo.ZoneInfo): """Constent ZoneInfo representation for tests.""" def __repr__(self): return f"ZoneInfo(key={self.key!r})" def doctest_print(obj): """doctest print""" if isinstance(obj, bytes): obj = obj.decode("UTF-8") print(str(obj).strip().replace("\r\n", "\n").replace("\r", "\n")) def doctest_import(name, *args, **kw): """Replace the import mechanism to skip the whole doctest if we import pytz.""" if name == "pytz": return pytz return __import__(name, *args, **kw) @pytest.fixture def env_for_doctest(monkeypatch): """Modify the environment to make doctests run.""" monkeypatch.setitem(sys.modules, "zoneinfo", zoneinfo) monkeypatch.setattr(zoneinfo, "ZoneInfo", DoctestZoneInfo) from icalendar.timezone.zoneinfo import ZONEINFO uid = uuid.UUID("d755cef5-2311-46ed-a0e1-6733c9e15c63", version=4) monkeypatch.setattr(uuid, "uuid4", lambda: uid) monkeypatch.setattr(ZONEINFO, "utc", zoneinfo.ZoneInfo("UTC")) return {"print": doctest_print} @pytest.fixture(params=timezone_ids.TZIDS) def tzid(request: pytest.FixtureRequest) -> str: """Return a timezone id to be used with pytz or zoneinfo. This goes through all the different timezones possible. """ return request.param icalendar-6.3.1/src/icalendar/tests/events/000077500000000000000000000000001501302773300206235ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/events/event_with_escaped_character1.ics000066400000000000000000000000741501302773300272610ustar00rootroot00000000000000BEGIN:VEVENT ORGANIZER;CN=Society\, 2014:that END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/event_with_escaped_character2.ics000066400000000000000000000000741501302773300272620ustar00rootroot00000000000000BEGIN:VEVENT ORGANIZER;CN=Society\\ 2014:that END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/event_with_escaped_character3.ics000066400000000000000000000000741501302773300272630ustar00rootroot00000000000000BEGIN:VEVENT ORGANIZER;CN=Society\; 2014:that END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/event_with_escaped_character4.ics000066400000000000000000000000741501302773300272640ustar00rootroot00000000000000BEGIN:VEVENT ORGANIZER;CN=Society\: 2014:that END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/event_with_escaped_characters.ics000066400000000000000000000001501501302773300273560ustar00rootroot00000000000000BEGIN:VEVENT ORGANIZER;CN=that\, that\; %th%%at%\ that\::это\, то\; that\ %th%%at%\: END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/event_with_recurrence.ics000066400000000000000000000003101501302773300257060ustar00rootroot00000000000000BEGIN:VEVENT DTSTART:19960401T010000 DTEND:19960401T020000 RRULE:FREQ=DAILY;COUNT=100 EXDATE:19960402T010000Z,19960403T010000Z,19960404T010000Z SUMMARY:A recurring event with exdates END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/event_with_recurrence_exdates_on_different_lines.ics000066400000000000000000000007101501302773300333430ustar00rootroot00000000000000BEGIN: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 icalendar-6.3.1/src/icalendar/tests/events/event_with_rsvp.ics000066400000000000000000000001111501302773300245420ustar00rootroot00000000000000BEGIN:VEVENT ATTENDEE;RSVP=TRUE:mailto:someone@example.com END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/event_with_unicode_fields.ics000066400000000000000000000003761501302773300265410ustar00rootroot00000000000000BEGIN: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 icalendar-6.3.1/src/icalendar/tests/events/event_with_unicode_organizer.ics000066400000000000000000000001261501302773300272640ustar00rootroot00000000000000BEGIN:VEVENT ORGANIZER;CN="Джон Доу":mailto:john.doe@example.org END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/issue_100_transformed_doctests_into_unittests.ics000066400000000000000000000000651501302773300325230ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY;LANGUAGE=ru:te END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/issue_101_icalendar_chokes_on_umlauts_in_organizer.ics000066400000000000000000000005471501302773300334140ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY:wichtiger termin 1 DTSTART:20130416T100000Z DTEND:20130416T110000Z DTSTAMP:20130416T092616Z UID:20130416112341.10064jz0k4j7uem8@acmenet.de CLASS:PUBLIC CREATED:20130416T092341Z LAST-MODIFIED:20130416T092341Z LOCATION:im büro ORGANIZER;CN="acme, ädmin":mailto:adm-acme@mydomain.de STATUS:CONFIRMED TRANSP:OPAQUE END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/issue_104_mark_events_broken.ics000066400000000000000000000002441501302773300267750ustar00rootroot00000000000000BEGIN:VEVENT DTSTART:20140401T000000Z DTEND:20140401T010000Z DTSTAMP:20140401T000000Z SUMMARY:Broken Eevnt CLASS:PUBLIC STATUS:CONFIRMED TRANSP:OPAQUE X END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/issue_112_missing_tzinfo_on_exdate.ics000066400000000000000000000012361501302773300302100ustar00rootroot00000000000000BEGIN: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 icalendar-6.3.1/src/icalendar/tests/events/issue_156_RDATE_with_PERIOD.ics000066400000000000000000000002521501302773300260610ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY:RDATE period DTSTART:19961230T020000Z DTEND:19961230T060000Z UID:rdate_period RDATE;VALUE=PERIOD:19970101T180000Z/19970102T070000Z END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/issue_156_RDATE_with_PERIOD_list.ics000066400000000000000000000003061501302773300271140ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY:RDATE period DTSTART:19961230T020000Z DTEND:19961230T060000Z UID:rdate_period RDATE;VALUE=PERIOD:19970101T180000Z/19970102T070000Z,19970109T180000Z/PT5H 30M END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/issue_157_removes_trailing_semicolon.ics000066400000000000000000000001341501302773300305460ustar00rootroot00000000000000BEGIN:VEVENT DTSTART:20150325T101010 RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU; END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/issue_184_broken_representation_of_period.ics000066400000000000000000000002031501302773300315520ustar00rootroot00000000000000BEGIN:VEVENT DTSTART:20150219T133000 DTSTAMP:20150219T133000 UID:1234567 RDATE;VALUE=PERIOD:20150219T133000/PT10H END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/issue_464_invalid_rdate.ics000066400000000000000000000003011501302773300257270ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY:RDATE period DTSTART:19961230T020000Z DTEND:19961230T060000Z UID:rdate_period RDATE;VALUE=PERIOD:19970101T180000Z/19970102T070000Z,199709T180000Z/PT5H30M END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/issue_53_description_parsed_properly.ics000066400000000000000000000013431501302773300306600ustar00rootroot00000000000000BEGIN: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 icalendar-6.3.1/src/icalendar/tests/events/issue_64_event_with_ascii_summary.ics000066400000000000000000000000521501302773300301420ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY:abcdef END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/issue_64_event_with_non_ascii_summary.ics000066400000000000000000000000521501302773300310140ustar00rootroot00000000000000BEGIN:VEVENT SUMMARY:åäö END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/issue_70_rrule_causes_attribute_error.ics000066400000000000000000000004271501302773300310340ustar00rootroot00000000000000BEGIN: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 icalendar-6.3.1/src/icalendar/tests/events/issue_82_expected_output.ics000066400000000000000000000001331501302773300262620ustar00rootroot00000000000000BEGIN:VEVENT ATTACH;ENCODING=BASE64;FMTTYPE=text/plain;VALUE=BINARY:dGV4dA== END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/rfc_9074_example_1.ics000066400000000000000000000005301501302773300245110ustar00rootroot00000000000000BEGIN:VEVENT CREATED:20210302T151004Z UID:AC67C078-CED3-4BF5-9726-832C3749F627 DTSTAMP:20210302T151004Z DTSTART;TZID=America/New_York:20210302T103000 DTEND;TZID=America/New_York:20210302T113000 SUMMARY:Meeting BEGIN:VALARM UID:8297C37D-BA2D-4476-91AE-C1EAA364F8E1 TRIGGER:-PT15M DESCRIPTION:Event reminder ACTION:DISPLAY END:VALARM END:VEVENTicalendar-6.3.1/src/icalendar/tests/events/rfc_9074_example_2.ics000066400000000000000000000011131501302773300245100ustar00rootroot00000000000000BEGIN:VEVENT CREATED:20210302T151004Z UID:AC67C078-CED3-4BF5-9726-832C3749F627 DTSTAMP:20210302T151516Z DTSTART;TZID=America/New_York:20210302T103000 DTEND;TZID=America/New_York:20210302T113000 SUMMARY:Meeting BEGIN:VALARM UID:8297C37D-BA2D-4476-91AE-C1EAA364F8E1 TRIGGER:-PT15M DESCRIPTION:Event reminder ACTION:DISPLAY ACKNOWLEDGED:20210302T151514Z END:VALARM BEGIN:VALARM UID:DE7B5C34-83FF-47FE-BE9E-FF41AE6DD097 TRIGGER;VALUE=DATE-TIME:20210302T152000Z RELATED-TO;RELTYPE=SNOOZE:8297C37D-BA2D-4476-91AE-C1EAA364F8E1 DESCRIPTION:Event reminder ACTION:DISPLAY END:VALARM END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/rfc_9074_example_3.ics000066400000000000000000000011131501302773300245110ustar00rootroot00000000000000BEGIN:VEVENT CREATED:20210302T151004Z UID:AC67C078-CED3-4BF5-9726-832C3749F627 DTSTAMP:20210302T152026Z DTSTART;TZID=America/New_York:20210302T103000 DTEND;TZID=America/New_York:20210302T113000 SUMMARY:Meeting BEGIN:VALARM UID:8297C37D-BA2D-4476-91AE-C1EAA364F8E1 TRIGGER:-PT15M DESCRIPTION:Event reminder ACTION:DISPLAY ACKNOWLEDGED:20210302T152024Z END:VALARM BEGIN:VALARM UID:87D690A7-B5E8-4EB4-8500-491F50AFE394 TRIGGER;VALUE=DATE-TIME:20210302T152500Z RELATED-TO;RELTYPE=SNOOZE:8297C37D-BA2D-4476-91AE-C1EAA364F8E1 DESCRIPTION:Event reminder ACTION:DISPLAY END:VALARM END:VEVENT icalendar-6.3.1/src/icalendar/tests/events/rfc_9074_example_4.ics000066400000000000000000000011501501302773300245130ustar00rootroot00000000000000BEGIN:VEVENT CREATED:20210302T151004Z UID:AC67C078-CED3-4BF5-9726-832C3749F627 DTSTAMP:20210302T152508Z DTSTART;TZID=America/New_York:20210302T103000 DTEND;TZID=America/New_York:20210302T113000 SUMMARY:Meeting BEGIN:VALARM UID:8297C37D-BA2D-4476-91AE-C1EAA364F8E1 TRIGGER:-PT15M DESCRIPTION:Event reminder ACTION:DISPLAY ACKNOWLEDGED:20210302T152507Z END:VALARM BEGIN:VALARM UID:87D690A7-B5E8-4EB4-8500-491F50AFE394 TRIGGER;VALUE=DATE-TIME:20210302T152500Z RELATED-TO;RELTYPE=SNOOZE:8297C37D-BA2D-4476-91AE-C1EAA364F8E1 DESCRIPTION:Event reminder ACTION:DISPLAY ACKNOWLEDGED:20210302T152507Z END:VALARM END:VEVENTicalendar-6.3.1/src/icalendar/tests/events/rfc_9074_example_proximity.ics000066400000000000000000000004441501302773300264210ustar00rootroot00000000000000BEGIN:VEVENT BEGIN:VALARM UID:77D80D14-906B-4257-963F-85B1E734DBB6 ACTION:DISPLAY TRIGGER;VALUE=DATE-TIME:19760401T005545Z DESCRIPTION:Remember to buy milk PROXIMITY:DEPART BEGIN:VLOCATION UID:123456-abcdef-98765432 NAME:Office URL:geo:40.443,-79.945;u=10 END:VLOCATION END:VALARM END:VEVENT icalendar-6.3.1/src/icalendar/tests/fuzzed/000077500000000000000000000000001501302773300206265ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/fuzzed/__init__.py000066400000000000000000000013211501302773300227340ustar00rootroot00000000000000"""This is a collection of test files that are generated from the fuzzer. The fuzzer finds the cases in which the icalendar module breaks. These test cases reproduce the failure. Some more tests can be added to make sure that the behavior works properly. """ def fuzz_calendar_v1( from_ical, calendar_string: str, multiple: bool, should_walk: bool ): """Take a from_ical function and reproduce the error. The calendar_string is a fuzzed input. """ cal = from_ical(calendar_string, multiple=multiple) if not multiple: cal = [cal] for c in cal: if should_walk: for event in c.walk("VEVENT"): event.to_ical() else: c.to_ical() generate_python_test_cases_from_downloaded_clusterfuzz_test_cases.sh000077500000000000000000000031541501302773300366410ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/fuzzed#!/usr/bin/env bash # # This script generates a test case from a test case file that was downloaded. # # You will need to follow the setup instructions here: # https://google.github.io/oss-fuzz/advanced-topics/reproducing/#reproduce-using-local-source-checkout # set -e HERE="`dirname \"$0\"`" OSS_FUZZ_DIRECTORY="$HOME/oss-fuzz" DOWNLOADS_DIRECTORY="$HOME/Downloads" LOCAL_ICALENDAR_DIRECTORY="$HERE/../../../../" PYTHON_TEST_CASE_DIRECTORY="$HERE/../calendars/" PROJECT_NAME="icalendar" echo "### Building Project $PROJECT_NAME" python "$OSS_FUZZ_DIRECTORY/infra/helper.py" build_fuzzers --sanitizer undefined "$PROJECT_NAME" "$LOCAL_ICALENDAR_DIRECTORY" # we capture the output OUTPUT="`mktemp`" # test case files look like this: # clusterfuzz-testcase-minimized-ical_fuzzer-4878676239712256 for testcase in "$DOWNLOADS_DIRECTORY/clusterfuzz-testcase-"* do echo "### Reproducing $testcase" python "$OSS_FUZZ_DIRECTORY/infra/helper.py" reproduce "$PROJECT_NAME" ical_fuzzer "$testcase" | tee "$OUTPUT" if [ $PIPESTATUS -eq 0 ] then echo "### Testcase fixed! $testcase" continue fi echo "### Testcase reproduced! $testcase" TEST_FILE_CONTENT="`cat \"$OUTPUT\" | sed -n '/--- start calendar ---/,/--- end calendar ---/{/--- start calendar ---/b;/--- end calendar ---/b;p}'`" if [ -z "$TEST_FILE_CONTENT" ] then echo "### No test file content for $testcase" exit 1 fi ICS_FILE="$PYTHON_TEST_CASE_DIRECTORY/`basename \"$testcase\"`.ics" # decode and ignore garbage, see https://stackoverflow.com/a/15490765/1320237 echo $TEST_FILE_CONTENT | base64 -di > /dev/null echo "Created $ICS_FILE" done icalendar-6.3.1/src/icalendar/tests/fuzzed/test_fuzzed_calendars.py000066400000000000000000000005271501302773300255660ustar00rootroot00000000000000"""This test tests all fuzzed calendars.""" import icalendar from icalendar.tests.fuzzed import fuzz_calendar_v1 def test_fuzz_v1(fuzz_v1_calendar): """Test a calendar.""" with open(fuzz_v1_calendar, "rb") as f: fuzz_calendar_v1( icalendar.Calendar.from_ical, f.read(), multiple=True, should_walk=True ) icalendar-6.3.1/src/icalendar/tests/hypothesis/000077500000000000000000000000001501302773300215165ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/hypothesis/test_fuzzing.py000066400000000000000000000017751501302773300246350ustar00rootroot00000000000000import string import unittest import hypothesis.strategies as st from hypothesis import given, settings from icalendar.parser import Contentline, Contentlines, Parameters def printable_characters(**kw): return st.text(st.characters(blacklist_categories=("Cc", "Cs"), **kw)) key = st.text(string.ascii_letters + string.digits, min_size=1) value = printable_characters(blacklist_characters='\\;:"') class TestFuzzing(unittest.TestCase): @given( lines=st.lists(st.tuples(key, st.dictionaries(key, value), value), min_size=1) ) @settings(max_examples=10**3) def test_main(self, lines): cl = Contentlines() for key, params, value in lines: try: params = Parameters(**params) except TypeError: # Happens when there is a random parameter 'self'... continue cl.append(Contentline.from_parts(key, params, value)) cl.append("") assert Contentlines.from_ical(cl.to_ical()) == cl icalendar-6.3.1/src/icalendar/tests/prop/000077500000000000000000000000001501302773300202775ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/prop/__init__.py000066400000000000000000000000521501302773300224050ustar00rootroot00000000000000"""Test the value types of properties.""" icalendar-6.3.1/src/icalendar/tests/prop/test_constructors.py000066400000000000000000000062721501302773300244670ustar00rootroot00000000000000from icalendar.prop import ( vBoolean, vInline, vUTCOffset, vCategory, vCalAddress, vWeekday, vDuration, vFloat, vGeo, vInt, vText, vMonth, vUTCOffset, vFrequency, vRecur, vDatetime, vUri, ) import datetime def test_param_vCategory(): obj = vCategory(["Work", "Personal"], params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vCategory) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vCalAddress(): obj = vCalAddress("mailto:jane_doe@example.com", params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vCalAddress) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vWeekday(): obj = vWeekday("2FR", params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vWeekday) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vBoolean(): obj = vBoolean(True, params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vBoolean) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vDuration(): td = datetime.timedelta(days=15, seconds=18020) obj = vDuration(td, params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vDuration) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vFloat(): obj = vFloat("1.333", params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vFloat) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vGeo(): obj = vGeo((37.386013, -122.082932), params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vGeo) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vInt(): obj = vInt("87", params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vInt) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vInline(): obj = vInline("sometxt", params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vInline) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vText(): obj = vText("sometxt", params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vText) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vMonth(): obj = vMonth(1, params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vMonth) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vUTCOffset(): obj = vUTCOffset( datetime.timedelta(days=-1, seconds=68400), params={"SOME_PARAM": "VALUE"} ) assert isinstance(obj, vUTCOffset) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vFrequency(): obj = vFrequency("DAILY", params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vFrequency) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vRecur(): obj = vRecur({"FREQ": ["DAILY"], "COUNT": [10]}, params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vRecur) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vDatetime(): dt = datetime.datetime(2025, 3, 16, 14, 30, 0, tzinfo=datetime.timezone.utc) obj = vDatetime(dt, params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vDatetime) assert obj.params["SOME_PARAM"] == "VALUE" def test_param_vUri(): obj = vUri("WWW.WESBITE.COM", params={"SOME_PARAM": "VALUE"}) assert isinstance(obj, vUri) assert obj.params["SOME_PARAM"] == "VALUE" icalendar-6.3.1/src/icalendar/tests/prop/test_identity_and_equality.py000066400000000000000000000035151501302773300263040ustar00rootroot00000000000000"""Test the identity and equality between properties.""" from datetime import date, datetime, time from icalendar import vDDDTypes from icalendar.timezone.zoneinfo import zoneinfo try: import pytz except ImportError: pytz = None from copy import deepcopy import pytest from dateutil import tz vDDDTypes_list = [ vDDDTypes( datetime( year=2022, month=7, day=22, hour=12, minute=7, tzinfo=zoneinfo.ZoneInfo("Europe/London"), ) ), vDDDTypes(datetime(year=2022, month=7, day=22, hour=12, minute=7)), vDDDTypes(datetime(year=2022, month=7, day=22, hour=12, minute=7, tzinfo=tz.UTC)), vDDDTypes(date(year=2022, month=7, day=22)), vDDDTypes(date(year=2022, month=7, day=23)), vDDDTypes(time(hour=22, minute=7, second=2)), ] if pytz: vDDDTypes_list.append( vDDDTypes( pytz.timezone("EST").localize( datetime(year=2022, month=7, day=22, hour=12, minute=7) ) ), ) def identity(x): return x @pytest.mark.parametrize( "map", [ deepcopy, identity, hash, ], ) @pytest.mark.parametrize("v_type", vDDDTypes_list) @pytest.mark.parametrize("other", vDDDTypes_list) def test_vDDDTypes_equivalance(map, v_type, other): if v_type is other: assert map(v_type) == map(other), f"identity implies equality: {map.__name__}()" assert map(v_type) == map(other), f"identity implies equality: {map.__name__}()" else: assert map(v_type) != map(other), f"expected inequality: {map.__name__}()" assert map(v_type) != map(other), f"expected inequality: {map.__name__}()" @pytest.mark.parametrize("v_type", vDDDTypes_list) def test_inequality_with_different_types(v_type): assert v_type != 42 assert v_type != "test" icalendar-6.3.1/src/icalendar/tests/prop/test_property_values.py000066400000000000000000000012111501302773300251460ustar00rootroot00000000000000"""Test that composed values are properly converted.""" from datetime import datetime from icalendar import Event def test_vDDDLists_timezone(tzp): """Test vDDDLists with timezone information.""" vevent = Event() dt1 = tzp.localize(datetime(2013, 1, 1), "Europe/Vienna") dt2 = tzp.localize(datetime(2013, 1, 2), "Europe/Vienna") dt3 = tzp.localize(datetime(2013, 1, 3), "Europe/Vienna") vevent.add("rdate", [dt1, dt2]) vevent.add("exdate", dt3) ical = vevent.to_ical() assert b"RDATE;TZID=Europe/Vienna:20130101T000000,20130102T000000" in ical assert b"EXDATE;TZID=Europe/Vienna:20130103T000000" in ical icalendar-6.3.1/src/icalendar/tests/prop/test_unit.py000066400000000000000000000331141501302773300226710ustar00rootroot00000000000000import unittest from datetime import date, datetime, time, timedelta from icalendar.parser import Parameters class TestProp(unittest.TestCase): def test_prop_vFloat(self): from icalendar.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") self.assertRaises(ValueError, vFloat.from_ical, "1s3") def test_prop_vInt(self): from icalendar.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 icalendar.prop import vDDDLists dt_list = vDDDLists.from_ical("19960402T010000Z") self.assertIsInstance(dt_list, list) self.assertEqual(len(dt_list), 1) self.assertIsInstance(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") instance = vDDDLists([]) self.assertNotEqual(instance, "value") def test_prop_vDate(self): from icalendar.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") self.assertRaises(ValueError, vDate.from_ical, "200102") def test_prop_vDuration(self): from icalendar.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) # calling to_ical twice should result in same output duration = vDuration(timedelta(days=-1, hours=-5)) self.assertEqual(duration.to_ical(), b"-P1DT5H") self.assertEqual(duration.to_ical(), b"-P1DT5H") def test_prop_vWeekday(self): from icalendar.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 icalendar.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") self.assertRaises(ValueError, vFrequency.from_ical, 234) def test_prop_vRecur(self): from icalendar.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;BYMONTH=1", ) r = vRecur.from_ical("FREQ=WEEKLY;INTERVAL=1;BYWEEKDAY=TH") self.assertEqual(r, {"FREQ": ["WEEKLY"], "INTERVAL": [1], "BYWEEKDAY": ["TH"]}) self.assertEqual(vRecur(r).to_ical(), b"FREQ=WEEKLY;INTERVAL=1;BYWEEKDAY=TH") # 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") # when key is not RFC-compliant, parse it as vText r = vRecur.from_ical("FREQ=MONTHLY;BYOTHER=TEXT;BYEASTER=-3") self.assertEqual(vRecur(r).to_ical(), b"FREQ=MONTHLY;BYEASTER=-3;BYOTHER=TEXT") def test_prop_vText(self): from icalendar.prop import vText self.assertEqual(vText("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 5545 support. t = vText("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"), "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"), "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 icalendar.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") self.assertRaises(ValueError, vTime, "263000") def test_prop_vUri(self): from icalendar.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 icalendar.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") self.assertRaises(ValueError, vGeo.from_ical, "1s3;1s3") def test_prop_vUTCOffset(self): from icalendar.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") self.assertRaises(ValueError, vUTCOffset, "0:00:00") def test_prop_vInline(self): from icalendar.prop import vInline self.assertEqual(vInline("Some text"), "Some text") self.assertEqual(vInline("Some text").to_ical(), b"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_vCategory(self): from icalendar.prop import vCategory catz = ["cat 1", "cat 2", "cat 3"] v_cat = vCategory(catz) self.assertEqual(v_cat.to_ical(), b"cat 1,cat 2,cat 3") self.assertEqual(vCategory.from_ical(v_cat.to_ical()), catz) c = vCategory(vCategory.from_ical("APPOINTMENT,EDUCATION")) cats = list(c) assert cats == ["APPOINTMENT", "EDUCATION"] def test_prop_TypesFactory(self): from icalendar.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", "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", "Rasmussen, Max M\xfcller"), b"Rasmussen\\, Max M\xc3\xbcller", ) self.assertEqual( factory.from_ical("cn", b"Rasmussen\\, Max M\xc3\xb8ller"), "Rasmussen, Max M\xf8ller", ) icalendar-6.3.1/src/icalendar/tests/prop/test_vBinary.py000066400000000000000000000022761501302773300233310ustar00rootroot00000000000000"""Test vBinary""" import pytest from icalendar import vBinary from icalendar.parser import Parameters def test_text(): txt = b"This is gibberish" txt_ical = b"VGhpcyBpcyBnaWJiZXJpc2g=" assert vBinary(txt).to_ical() == txt_ical assert vBinary.from_ical(txt_ical) == txt def test_binary(): txt = b"Binary data \x13 \x56" txt_ical = b"QmluYXJ5IGRhdGEgEyBW" assert vBinary(txt).to_ical() == txt_ical assert vBinary.from_ical(txt_ical) == txt def test_param(): assert isinstance(vBinary("txt").params, Parameters) assert vBinary("txt").params == {"VALUE": "BINARY", "ENCODING": "BASE64"} def test_long_data(): """Long data should not have line breaks, as that would interfere""" txt = b"a" * 99 txt_ical = b"YWFh" * 33 assert vBinary(txt).to_ical() == txt_ical assert vBinary.from_ical(txt_ical) == txt def test_repr(): instance = vBinary("value") assert repr(instance) == "vBinary(b'dmFsdWU=')" def test_from_ical(): with pytest.raises(ValueError, match="Not valid base 64 encoding."): vBinary.from_ical("value") with pytest.raises(ValueError, match="Not valid base 64 encoding."): vBinary.from_ical("áèਮ") icalendar-6.3.1/src/icalendar/tests/prop/test_vBoolean.py000066400000000000000000000006671501302773300234660ustar00rootroot00000000000000import pytest from icalendar.prop import vBoolean def test_true(): assert vBoolean(True).to_ical() == b"TRUE" def test_false(): assert vBoolean(0).to_ical() == b"FALSE" def test_roundtrip(): assert vBoolean.from_ical(vBoolean(True).to_ical()) == True assert vBoolean.from_ical("true") == True def test_error(): """Error: key not exists""" with pytest.raises(ValueError): vBoolean.from_ical("ture") icalendar-6.3.1/src/icalendar/tests/prop/test_vCalAddress.py000066400000000000000000000026141501302773300241060ustar00rootroot00000000000000from icalendar.parser import Parameters from icalendar.prop import vCalAddress txt = b"MAILTO:maxm@mxm.dk" a = vCalAddress(txt) a.params["cn"] = "Max M" def test_to_ical(): assert a.to_ical() == txt def test_params(): assert isinstance(a.params, Parameters) assert a.params == {"CN": "Max M"} def test_from_ical(): assert vCalAddress.from_ical(txt) == "MAILTO:maxm@mxm.dk" def test_repr(): instance = vCalAddress("value") assert repr(instance) == "vCalAddress('value')" def test_email_malformed(): """Sometimes, people forget to add mailto that.""" address = vCalAddress("me@you.we") assert address.email == "me@you.we" def test_email_mailto(): """Email with a normal mailto link.""" address = vCalAddress("mailto:icalendar@email.list") assert address.email == "icalendar@email.list" def test_capital_email(): """mailto can be capital letters.""" address = vCalAddress("MAILTO:yemaya@posteo.net") assert address.email == "yemaya@posteo.net" def test_name(): """We want the name, too!""" address = vCalAddress("MAILTO:yemaya@posteo.net") assert address.name == "" address.params["CN"] = "name!" assert address.name == "name!" def test_set_the_name(): address = vCalAddress("MAILTO:yemaya@posteo.net") address.name = "Yemaya :)" assert address.name == "Yemaya :)" assert address.params["CN"] == "Yemaya :)" icalendar-6.3.1/src/icalendar/tests/prop/test_vDDDTypes.py000066400000000000000000000017351501302773300235240ustar00rootroot00000000000000from datetime import date, datetime, time, timedelta import pytest from icalendar.prop import vDDDTypes def test_instance(): assert isinstance(vDDDTypes.from_ical("20010101T123000"), datetime) assert isinstance(vDDDTypes.from_ical("20010101"), date) def test_datetime_with_timezone(tzp): assert vDDDTypes.from_ical("20010101T123000Z") == tzp.localize_utc( datetime(2001, 1, 1, 12, 30) ) def test_timedelta(): assert vDDDTypes.from_ical("P31D") == timedelta(31) assert vDDDTypes.from_ical("-P31D") == timedelta(-31) def test_bad_input(): with pytest.raises(ValueError): vDDDTypes(42) def test_time_from_string(): assert vDDDTypes.from_ical("123000") == time(12, 30) assert isinstance(vDDDTypes.from_ical("123000"), time) def test_invalid_period_to_ical(): invalid_period = (datetime(2000, 1, 1), datetime(2000, 1, 2), datetime(2000, 1, 2)) with pytest.raises(ValueError): vDDDTypes(invalid_period).to_ical() icalendar-6.3.1/src/icalendar/tests/prop/test_vDatetime.py000066400000000000000000000026111501302773300236320ustar00rootroot00000000000000from datetime import datetime import pytest from icalendar.prop import vDatetime def test_to_ical(): assert vDatetime(datetime(2001, 1, 1, 12, 30, 0)).to_ical() == b"20010101T123000" def test_from_ical(): assert vDatetime.from_ical("20000101T120000") == datetime(2000, 1, 1, 12, 0) assert vDatetime.from_ical("20010101T000000") == datetime(2001, 1, 1, 0, 0) def test_to_ical_utc(tzp): dutc = tzp.localize_utc(datetime(2001, 1, 1, 12, 30, 0)) assert vDatetime(dutc).to_ical() == b"20010101T123000Z" def test_to_ical_utc_1899(tzp): dutc = tzp.localize_utc(datetime(1899, 1, 1, 12, 30, 0)) assert vDatetime(dutc).to_ical() == b"18990101T123000Z" def test_bad_ical(): with pytest.raises(ValueError): vDatetime.from_ical("20010101T000000A") def test_roundtrip(): utc = vDatetime.from_ical("20010101T000000Z") assert vDatetime(utc).to_ical() == b"20010101T000000Z" def test_transition(tzp): # 1 minute before transition to DST dat = vDatetime.from_ical("20120311T015959", "America/Denver") assert dat.strftime("%Y%m%d%H%M%S %z") == "20120311015959 -0700" # After transition to DST dat = vDatetime.from_ical("20120311T030000", "America/Denver") assert dat.strftime("%Y%m%d%H%M%S %z") == "20120311030000 -0600" dat = vDatetime.from_ical("20101010T000000", "Europe/Vienna") assert vDatetime(dat).to_ical() == b"20101010T000000" icalendar-6.3.1/src/icalendar/tests/prop/test_vPeriod.py000066400000000000000000000041531501302773300233230ustar00rootroot00000000000000import unittest from datetime import datetime, timedelta import pytest from icalendar.prop import vPeriod class TestProp(unittest.TestCase): def test_one_day(self): # 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") def test_roundtrip(self): 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)), ) def test_round_trip_with_absolute_time(self): p = vPeriod.from_ical("20000101T000000Z/20000102T000000Z") self.assertEqual(vPeriod(p).to_ical(), b"20000101T000000Z/20000102T000000Z") def test_bad_input(self): self.assertRaises(ValueError, vPeriod.from_ical, "20000101T000000/Psd31D") def test_timezoned(tzp): start = tzp.localize(datetime(2000, 1, 1), "Europe/Copenhagen") end = tzp.localize(datetime(2000, 1, 2), "Europe/Copenhagen") per = (start, end) assert vPeriod(per).to_ical() == b"20000101T000000/20000102T000000" assert vPeriod(per).params["TZID"] == "Europe/Copenhagen" def test_timezoned_with_timedelta(tzp): p = vPeriod( (tzp.localize(datetime(2000, 1, 1), "Europe/Copenhagen"), timedelta(days=31)) ) assert p.to_ical() == b"20000101T000000/P31D" @pytest.mark.parametrize( "params", [ ("20000101T000000", datetime(2000, 1, 2)), (datetime(2000, 1, 1), "20000102T000000"), (datetime(2000, 1, 2), datetime(2000, 1, 1)), (datetime(2000, 1, 2), timedelta(-1)), ], ) def test_invalid_parameters(params): """The parameters are of wrong type or of wrong order.""" with pytest.raises(ValueError): vPeriod(params) icalendar-6.3.1/src/icalendar/tests/prop/test_vWeekday.py000066400000000000000000000011561501302773300234720ustar00rootroot00000000000000import pytest from icalendar.prop import vWeekday def test_simple(): weekday = vWeekday("SU") assert weekday.to_ical() == b"SU" assert weekday.weekday == "SU" assert weekday.relative is None def test_relative(): weekday = vWeekday("-1MO") assert weekday.to_ical() == b"-1MO" assert weekday.weekday == "MO" assert weekday.relative == -1 def test_roundtrip(): assert vWeekday.from_ical(vWeekday("+2TH").to_ical()) == "+2TH" def test_error(): """Error: Expected weekday abbrevation, got: \"-100MO\" """ with pytest.raises(ValueError): vWeekday.from_ical("-100MO") icalendar-6.3.1/src/icalendar/tests/prop/test_windows_to_olson_mapping.py000066400000000000000000000013221501302773300270270ustar00rootroot00000000000000"""Test the mappings from windows to olson tzids""" from datetime import datetime import pytest from icalendar import vDatetime from icalendar.timezone.windows_to_olson import WINDOWS_TO_OLSON def test_windows_timezone(tzp): """Test that the timezone is mapped correctly to olson.""" dt = vDatetime.from_ical("20170507T181920", "Eastern Standard Time") expected = tzp.localize(datetime(2017, 5, 7, 18, 19, 20), "America/New_York") assert dt.tzinfo == expected.tzinfo assert dt == expected @pytest.mark.parametrize("olson_id", WINDOWS_TO_OLSON.values()) def test_olson_names(tzp, olson_id): """test if all mappings actually map to valid tzids""" assert tzp.timezone(olson_id) is not None icalendar-6.3.1/src/icalendar/tests/test_bom_calendar.py000066400000000000000000000002371501302773300233400ustar00rootroot00000000000000def test_bom_calendar(calendars): assert calendars.bom_calendar.walk( "VCALENDAR" ), "Unable to parse a calendar starting with an Unicode BOM" icalendar-6.3.1/src/icalendar/tests/test_cli_tool.py000066400000000000000000000052501501302773300225360ustar00rootroot00000000000000import unittest from datetime import datetime from icalendar import Calendar, cli try: import zoneinfo except ModuleNotFoundError: from backports import zoneinfo INPUT = """ BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN BEGIN:VEVENT SUMMARY:Test Summary ORGANIZER:organizer@test.test ATTENDEE:attendee1@example.com ATTENDEE:attendee2@test.test COMMENT:Comment DTSTART;TZID=Europe/Warsaw:20220820T103400 DTEND;TZID=Europe/Warsaw:20220820T113400 LOCATION:New Amsterdam, 1000 Sunrise Test Street DESCRIPTION: Test Description END:VEVENT BEGIN:VEVENT ORGANIZER:organizer@test.test ATTENDEE:attendee1@example.com SUMMARY:Test summary DTSTART;TZID=Europe/Warsaw:20220820T200000 DTEND;TZID=Europe/Warsaw:20220820T203000 LOCATION:New Amsterdam, 1010 Test Street DESCRIPTION:Test Description\\nThis one is multiline END:VEVENT BEGIN:VEVENT UID:1 SUMMARY:TEST DTSTART:20220511 DURATION:P5D END:VEVENT END:VCALENDAR """ def local_datetime(dt): return ( datetime.strptime(dt, "%Y%m%dT%H%M%S") .replace(tzinfo=zoneinfo.ZoneInfo("Europe/Warsaw")) .astimezone() .strftime("%c") ) # datetimes are displayed in the local timezone, so we cannot just hardcode them firststart = local_datetime("20220820T103400") firstend = local_datetime("20220820T113400") secondstart = local_datetime("20220820T200000") secondend = local_datetime("20220820T203000") PROPER_OUTPUT = f""" Organizer: organizer Attendees: attendee1 attendee2 Summary : Test Summary Starts : {firststart} End : {firstend} Duration : 1:00:00 Location : New Amsterdam, 1000 Sunrise Test Street Comment : Comment Description: Test Description Organizer: organizer Attendees: attendee1 Summary : Test summary Starts : {secondstart} End : {secondend} Duration : 0:30:00 Location : New Amsterdam, 1010 Test Street Comment : Description: Test Description This one is multiline Organizer: Attendees: Summary : TEST Starts : Wed May 11 00:00:00 2022 End : Mon May 16 00:00:00 2022 Duration : 5 days, 0:00:00 Location : Comment : Description: """ class CLIToolTest(unittest.TestCase): def test_output_is_proper(self): self.maxDiff = None calendar = Calendar.from_ical(INPUT) output = "" for event in calendar.walk("vevent"): output += cli.view(event) + "\n\n" self.assertEqual(PROPER_OUTPUT, output) if __name__ == "__main__": unittest.main() icalendar-6.3.1/src/icalendar/tests/test_components_break_on_bad_ics.py000066400000000000000000000031021501302773300264150ustar00rootroot00000000000000import pytest def test_ignore_exceptions_on_broken_events_issue_104(events): """Issue #104 - line parsing error in a VEVENT (which has ignore_exceptions). Should mark the event broken but not raise an exception. https://github.com/collective/icalendar/issues/104 """ assert events.issue_104_mark_events_broken.errors == [ (None, "Content line could not be parsed into parts: 'X': Invalid content line") ] def test_dont_ignore_exceptions_on_broken_calendars_issue_104(calendars): """Issue #104 - line parsing error in a VCALENDAR (which doesn't have ignore_exceptions). Should raise an exception. """ with pytest.raises(ValueError): calendars.issue_104_broken_calendar def test_rdate_dosent_become_none_on_invalid_input_issue_464(events): """Issue #464 - [BUG] RDATE can become None if value is invalid https://github.com/collective/icalendar/issues/464 """ assert ( "RDATE", "Expected period format, got: 199709T180000Z/PT5H30M", ) in events.issue_464_invalid_rdate.errors assert b"RDATE:None" not in events.issue_464_invalid_rdate.to_ical() @pytest.mark.parametrize( "calendar_name", [ "big_bad_calendar", "small_bad_calendar", "multiple_calendar_components", "pr_480_summary_with_colon", ], ) def test_error_message_doesnt_get_too_big(calendars, calendar_name): with pytest.raises(ValueError) as exception: calendars[calendar_name] # Ignore part before first : for the test. assert len(str(exception).split(": ", 1)[1]) <= 100 icalendar-6.3.1/src/icalendar/tests/test_create_release.sh000077500000000000000000000013111501302773300236540ustar00rootroot00000000000000#!/bin/sh # # Create a release file and test it. # set -e cd "`dirname \"$0\"`" cd "../../.." rm -rf dist pip3 install build python3 -m build archive=`echo dist/icalendar-*.tar.gz` if ! [ -f "$archive" ]; then echo "ERROR: Cannot find distribution archive '$archive'." exit 1 fi if tar -tf "$archive" | grep -q 'fuzzing/'; then echo "ERROR: Fuzzing files are included in the release." echo " See https://github.com/collective/icalendar/pull/569" exit 1 fi if ! tar -tf "$archive" | grep -q '/docs/'; then echo "ERROR: The documentation is not included in the release, but should be." echo " See https://github.com/collective/icalendar/issues/712" exit 1 fi echo "Checks passed." icalendar-6.3.1/src/icalendar/tests/test_encoding.py000066400000000000000000000070251501302773300225220ustar00rootroot00000000000000import datetime import pytest @pytest.mark.parametrize( ("field", "expected_value"), [ ("PRODID", "-//Plönë.org//NONSGML plone.app.event//EN"), ("X-WR-CALDESC", "test non ascii: äöü ÄÖÜ €"), ], ) def test_calendar_from_ical_respects_unicode(field, expected_value, calendars): cal = calendars.calendar_with_unicode assert cal[field].to_ical().decode("utf-8") == expected_value @pytest.mark.parametrize( ("test_input", "field", "expected_value"), [ ("event_with_unicode_fields", "SUMMARY", "Non-ASCII Test: ÄÖÜ äöü €"), ( "event_with_unicode_fields", "DESCRIPTION", "icalendar should be able to handle non-ascii: €äüöÄÜÖ.", ), ("event_with_unicode_fields", "LOCATION", "Tribstrül"), # Non-unicode characters in summary # https://github.com/collective/icalendar/issues/64 ("issue_64_event_with_non_ascii_summary", "SUMMARY", "åäö"), # Unicode characters in summary ("issue_64_event_with_ascii_summary", "SUMMARY", "abcdef"), ], ) def test_event_from_ical_respects_unicode(test_input, field, expected_value, events): event = events[test_input] assert event[field].to_ical().decode("utf-8") == expected_value @pytest.mark.parametrize( ("test_input", "expected_output"), [ # chokes on umlauts in ORGANIZER # https://github.com/collective/icalendar/issues/101 ("issue_101_icalendar_chokes_on_umlauts_in_organizer", "acme, ädmin"), ("event_with_unicode_organizer", "Джон Доу"), ], ) def test_events_parameter_unicoded(events, test_input, expected_output): assert events[test_input]["ORGANIZER"].params["CN"] == expected_output def test_parses_event_with_non_ascii_tzid_issue_237(calendars, in_timezone): """Issue #237 - Fail to parse timezone with non-ascii TZID see https://github.com/collective/icalendar/issues/237 """ start = calendars.issue_237_fail_to_parse_timezone_with_non_ascii_tzid.walk( "VEVENT" )[0].decoded("DTSTART") expected = in_timezone(datetime.datetime(2017, 5, 11, 13, 30), "America/Sao_Paulo") assert not calendars.issue_237_fail_to_parse_timezone_with_non_ascii_tzid.errors assert start == expected def test_parses_timezone_with_non_ascii_tzid_issue_237(timezones): """Issue #237 - Fail to parse timezone with non-ascii TZID see https://github.com/collective/icalendar/issues/237 """ assert timezones.issue_237_brazilia_standard["tzid"] == "(UTC-03:00) Brasília" @pytest.mark.parametrize("timezone_name", ["standard", "daylight"]) def test_parses_timezone_with_non_ascii_tzname_issue_273(timezones, timezone_name): """Issue #237 - Fail to parse timezone with non-ascii TZID see https://github.com/collective/icalendar/issues/237 """ assert ( timezones.issue_237_brazilia_standard.walk(timezone_name)[0]["TZNAME"] == f"Brasília {timezone_name}" ) def test_broken_property(calendars): """ Test if error messages are encoded properly. """ for event in calendars.broken_ical.walk("vevent"): assert len(event.errors) == 1, "Not the right amount of errors." error = event.errors[0][1] assert error.startswith("Content line could not be parsed into parts") def test_apple_xlocation(calendars): """ Test if we support base64 encoded binary data in parameter values. """ for event in calendars.x_location.walk("vevent"): assert len(event.errors) == 0, "Got too many errors" icalendar-6.3.1/src/icalendar/tests/test_equality.py000066400000000000000000000130141501302773300225640ustar00rootroot00000000000000"""Test the equality and inequality of components.""" import contextlib import copy try: from pytz import UnknownTimeZoneError except ImportError: class UnknownTimeZoneError(Exception): pass from datetime import date, datetime, time, timedelta import pytest from icalendar.prop import ( vBinary, vBoolean, vCategory, vDate, vDatetime, vDDDLists, vDDDTypes, vDuration, vGeo, vPeriod, vText, vTime, ) def assert_equal(actual_value, expected_value): """Make sure both values are equal""" assert actual_value == expected_value assert expected_value == actual_value def assert_not_equal(actual_value, expected_value): """Make sure both values are not equal""" assert actual_value != expected_value assert expected_value != actual_value def test_parsed_calendars_are_equal_if_parsed_again(ics_file, tzp): """Ensure that a calendar equals the same calendar. ics -> calendar -> ics -> same calendar """ copy_of_calendar = ics_file.__class__.from_ical(ics_file.to_ical()) assert_equal(copy_of_calendar, ics_file) def test_parsed_calendars_are_equal_if_from_same_source(ics_file, tzp): """Ensure that a calendar equals the same calendar. ics -> calendar ics -> same calendar """ cal1 = ics_file.__class__.from_ical(ics_file.raw_ics) cal2 = ics_file.__class__.from_ical(ics_file.raw_ics) assert_equal(cal1, cal2) def test_copies_are_equal(ics_file, tzp): """Ensure that copies are equal.""" copy1 = ics_file.copy() copy1.subcomponents = ics_file.subcomponents copy2 = ics_file.copy() copy2.subcomponents = ics_file.subcomponents[:] assert_equal(copy1, copy2) assert_equal(copy1, ics_file) assert_equal(copy2, ics_file) def test_copy_does_not_copy_subcomponents(calendars, tzp): """If we copy the subcomponents, assumptions around copies will be broken.""" assert calendars.timezoned.subcomponents assert not calendars.timezoned.copy().subcomponents def test_deep_copies_are_equal(ics_file, tzp): """Ensure that deep copies are equal. Ignore errors when a custom time zone is used. This is still covered by the parsing test. """ if ( ics_file.source_file == "issue_722_timezone_transition_ambiguity.ics" and tzp.uses_zoneinfo() ): pytest.skip("This test fails for now.") with contextlib.suppress(UnknownTimeZoneError): assert_equal(copy.deepcopy(ics_file), copy.deepcopy(ics_file)) with contextlib.suppress(UnknownTimeZoneError): assert_equal(copy.deepcopy(ics_file), ics_file) def test_vGeo(): """Check the equality of vGeo.""" assert_equal(vGeo(("100", "12.33")), vGeo(("100.00", "12.330"))) assert_not_equal(vGeo(("100", "12.331")), vGeo(("100.00", "12.330"))) assert_not_equal(vGeo(("10", "12.33")), vGeo(("100.00", "12.330"))) def test_vBinary(): assert_equal(vBinary("asd"), vBinary("asd")) assert_not_equal(vBinary("asdf"), vBinary("asd")) def test_vBoolean(): assert_equal(vBoolean.from_ical("TRUE"), vBoolean.from_ical("TRUE")) assert_equal(vBoolean.from_ical("FALSE"), vBoolean.from_ical("FALSE")) assert_not_equal(vBoolean.from_ical("TRUE"), vBoolean.from_ical("FALSE")) def test_vCategory(): assert_equal(vCategory("HELLO"), vCategory("HELLO")) assert_equal(vCategory(["a", "b"]), vCategory(["a", "b"])) assert_not_equal(vCategory(["a", "b"]), vCategory(["a", "b", "c"])) def test_vText(): assert_equal(vText("HELLO"), vText("HELLO")) assert_not_equal(vText("HELLO1"), vText("HELLO")) @pytest.mark.parametrize( ("vType", "v1", "v2"), [ (vDatetime, datetime(2023, 11, 1, 10, 11), datetime(2023, 11, 1, 10, 10)), (vDate, date(2023, 11, 1), date(2023, 10, 31)), (vDuration, timedelta(3, 11, 1), timedelta(23, 10, 31)), ( vPeriod, (datetime(2023, 11, 1, 10, 11), timedelta(3, 11, 1)), (datetime(2023, 11, 1, 10, 11), timedelta(23, 10, 31)), ), ( vPeriod, (datetime(2023, 11, 1, 10, 1), timedelta(3, 11, 1)), (datetime(2023, 11, 1, 10, 11), timedelta(3, 11, 1)), ), ( vPeriod, (datetime(2023, 11, 1, 10, 1), datetime(2023, 11, 1, 10, 3)), (datetime(2023, 11, 1, 10, 1), datetime(2023, 11, 1, 10, 2)), ), (vTime, time(10, 10, 10), time(10, 10, 11)), ], ) @pytest.mark.parametrize("eq", ["==", "!="]) @pytest.mark.parametrize("cls1", [0, 1]) @pytest.mark.parametrize("cls2", [0, 1]) @pytest.mark.parametrize("hash", [lambda x: x, hash]) def test_vDDDTypes_and_others(vType, v1, v2, cls1, cls2, eq, hash): """Check equality and inequality.""" t1 = (vType, vDDDTypes)[cls1] t2 = (vType, vDDDTypes)[cls2] if eq == "==": assert hash(v1) == hash(v1) assert hash(t1(v1)) == hash(t2(v1)) assert hash(t1(v1)) == hash(t2(v1)) else: assert hash(v1) != hash(v2) assert hash(t1(v1)) != hash(t2(v2)) def test_repr_vDDDTypes(): assert "vDDDTypes" in repr(vDDDTypes(timedelta(3, 11, 1))) vDDDLists_examples = [ # noqa: N816 vDDDLists([]), vDDDLists([datetime(2023, 11, 1, 10, 1)]), vDDDLists([datetime(2023, 11, 1, 10, 1), date(2023, 11, 1)]), ] @pytest.mark.parametrize("l1", vDDDLists_examples) @pytest.mark.parametrize("l2", vDDDLists_examples) def test_vDDDLists(l1, l2): """Check the equality functions of vDDDLists.""" equal = l1 is l2 l2 = copy.deepcopy(l2) assert equal == (l1 == l2) assert equal != (l1 != l2) icalendar-6.3.1/src/icalendar/tests/test_examples.py000066400000000000000000000050261501302773300225510ustar00rootroot00000000000000"""tests ensuring that *the* way of doing things works""" import datetime import pytest from icalendar import Calendar, Event, Timezone def test_creating_calendar_with_unicode_fields(calendars, utc): """create a calendar with events that contain unicode characters in their fields""" cal = Calendar() cal.add("PRODID", "-//Plönë.org//NONSGML plone.app.event//EN") cal.add("VERSION", "2.0") cal.add("X-WR-CALNAME", "äöü ÄÖÜ €") cal.add("X-WR-CALDESC", "test non ascii: äöü ÄÖÜ €") cal.add("X-WR-RELCALID", "12345") event = Event() event.add("DTSTART", datetime.datetime(2010, 10, 10, 10, 0, 0, tzinfo=utc)) event.add("DTEND", datetime.datetime(2010, 10, 10, 12, 0, 0, tzinfo=utc)) event.add("CREATED", datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=utc)) event.add("UID", "123456") event.add("SUMMARY", "Non-ASCII Test: ÄÖÜ äöü €") event.add("DESCRIPTION", "icalendar should be able to de/serialize non-ascii.") event.add("LOCATION", "Tribstrül") cal.add_component(event) # test_create_event_simple event1 = Event() event1.add("DTSTART", datetime.datetime(2010, 10, 10, 0, 0, 0, tzinfo=utc)) event1.add("SUMMARY", "åäö") cal.add_component(event1) # test_unicode_parameter_name # test for issue #80 https://github.com/collective/icalendar/issues/80 event2 = Event() event2.add("DESCRIPTION", "äöüßÄÖÜ") cal.add_component(event2) assert cal.to_ical() == calendars.created_calendar_with_unicode_fields.raw_ics @pytest.mark.parametrize( ("component", "example"), [ (Calendar, "example"), (Calendar, "example.ics"), (Event, "event_with_rsvp"), (Timezone, "pacific_fiji"), ], ) def test_component_has_examples(tzp, calendars, timezones, events, component, example): """Check that the examples function works.""" mapping = {Calendar: calendars, Event: events, Timezone: timezones} example_component = component.example(example) expected_component = mapping[component][example] assert example_component == expected_component def test_invalid_examples_lists_the_others(): """We need a bit of guidance here.""" with pytest.raises(ValueError) as e: Calendar.example("does not exist") assert "example.ics" in str(e.value) @pytest.mark.parametrize("component", [Calendar, Event, Timezone]) def test_default_example(component): """Check that we have a default example.""" example = component.example() assert isinstance(example, component) icalendar-6.3.1/src/icalendar/tests/test_icalendar.py000066400000000000000000000253361501302773300226630ustar00rootroot00000000000000import unittest from icalendar.parser import ( Contentline, Contentlines, Parameters, dquote, foldline, q_join, q_split, ) from icalendar.prop import vText class IcalendarTestCase(unittest.TestCase): def setUp(self): if not hasattr(self, "assertRaisesRegex"): self.assertRaisesRegex = self.assertRaisesRegexp def test_long_lines(self): 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): 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 " ), ) # https://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 allowed 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), "SUMMARY:INternational char æ ø å" ) # A value can also be unicode parts = ("SUMMARY", Parameters(), vText("INternational char æ ø å")) self.assertEqual( Contentline.from_parts(*parts), "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.assertRaisesRegex( ValueError, "Content line could not be parsed into parts" ): Contentline("ATTENDEE;maxm@example.com").parts() # Another failure: with self.assertRaisesRegex( 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.assertRaisesRegex( 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"), ) contains_base64 = ( b"X-APPLE-STRUCTURED-LOCATION;" b'VALUE=URI;X-ADDRESS="Kaiserliche Hofburg, 1010 Wien";' b"X-APPLE-MAPKIT-HANDLE=CAESxQEZgr3QZXJyZWljaA==;" b"X-APPLE-RADIUS=328.7978217977285;X-APPLE-REFERENCEFRAME=1;" b"X-TITLE=Heldenplatz:geo:48.206686,16.363235" ) self.assertEqual( Contentline(contains_base64, strict=True).parts(), ( "X-APPLE-STRUCTURED-LOCATION", Parameters( { "X-APPLE-RADIUS": "328.7978217977285", "X-ADDRESS": "Kaiserliche Hofburg, 1010 Wien", "X-APPLE-REFERENCEFRAME": "1", "X-TITLE": "HELDENPLATZ", "X-APPLE-MAPKIT-HANDLE": "CAESXQEZGR3QZXJYZWLJAA==", "VALUE": "URI", } ), "geo:48.206686,16.363235", ), ) def test_fold_line(self): self.assertEqual(foldline("foo"), "foo") self.assertEqual( foldline( "Lorem ipsum dolor sit amet, consectetur adipiscing " "elit. Vestibulum convallis imperdiet dui posuere." ), ( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " "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("привет".encode(), limit=3) self.assertEqual(foldline("foobar", limit=4), "foo\r\n bar") self.assertEqual( foldline( "Lorem ipsum dolor sit amet, consectetur adipiscing elit" ". Vestibulum convallis imperdiet dui posuere." ), ( "Lorem ipsum dolor sit amet, consectetur adipiscing elit." " Vestibulum conval\r\n lis imperdiet dui posuere." ), ) self.assertEqual( foldline("DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ"), "DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭ\r\n ЮЯ", ) def test_value_double_quoting(self): self.assertEqual(dquote("Max"), "Max") self.assertEqual(dquote("Rasmussen, Max"), '"Rasmussen, Max"') self.assertEqual(dquote("name:value"), '"name:value"') def test_q_split(self): self.assertEqual( q_split('Max,Moller,"Rasmussen, Max"'), ["Max", "Moller", '"Rasmussen, Max"'], ) def test_q_split_bin(self): for s in ("X-SOMETHING=ABCDE==", ",,,"): for maxsplit in range(-1, 3): self.assertEqual( q_split(s, "=", maxsplit=maxsplit), s.split("=", maxsplit) ) def test_q_join(self): self.assertEqual( q_join(["Max", "Moller", "Rasmussen, Max"]), 'Max,Moller,"Rasmussen, Max"' ) icalendar-6.3.1/src/icalendar/tests/test_issue_116.py000066400000000000000000000016651501302773300224570ustar00rootroot00000000000000import icalendar def test_issue_116(): """Issue #116/#117 - How to add 'X-APPLE-STRUCTURED-LOCATION' https://github.com/collective/icalendar/issues/116 https://github.com/collective/icalendar/issues/117 """ 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", }, ) assert 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 assert event.to_ical() == icalendar.Event.from_ical(event.to_ical()).to_ical() icalendar-6.3.1/src/icalendar/tests/test_issue_165_missing_event.py000066400000000000000000000005131501302773300254040ustar00rootroot00000000000000"""Issue #165 - Problem parsing a file with event recurring on weekdays https://github.com/collective/icalendar/issues/165 """ def test_issue_165_missing_event(calendars): events = list(calendars.issue_165_missing_event.walk("VEVENT")) assert len(events) == 1, "There was an event missing from the parsed events' list." icalendar-6.3.1/src/icalendar/tests/test_issue_168_parsing_invalid_calendars_no_warning.py000066400000000000000000000010611501302773300321420ustar00rootroot00000000000000"""Issue #168 - Parsing invalid icalendars fails without any warning https://github.com/collective/icalendar/issues/168 """ def test_issue_168_parsing_inavlid_calendars_no_warning(calendars): expected_error = ( None, "Content line could not be parsed into parts: 'X-APPLE-RADIUS=49.91307046514149': X-APPLE-RADIUS=49.91307046514149", ) assert expected_error in calendars.issue_168_input.walk("VEVENT")[0].errors assert ( calendars.issue_168_input.to_ical() == calendars.issue_168_expected_output.raw_ics ) icalendar-6.3.1/src/icalendar/tests/test_issue_218_parse_calendar.py000066400000000000000000000017271501302773300255040ustar00rootroot00000000000000"""Parse the calendar and make sure the timezone is used. See https://github.com/collective/icalendar/issues/218 """ from __future__ import annotations from datetime import datetime, timedelta from typing import TYPE_CHECKING import pytest from icalendar.timezone.tzid import tzid_from_dt if TYPE_CHECKING: from icalendar import Event @pytest.fixture() def event(calendars) -> Event: """The event to check.""" return calendars.issue_218_bad_tzid.events[0] def test_event_has_start_and_end(event : Event): """The calendar should be parsed and the start and end have a timezone.""" assert event.start.replace(tzinfo=None) == datetime(2017, 2, 28, 23, 00) assert event.end.replace(tzinfo=None) == datetime(2017, 2, 28, 23, 30) def test_timezone(event:Event): """The event uses a timezone.""" assert event.start.tzinfo == event.end.tzinfo assert tzid_from_dt(event.start) == "UTC+11" assert event.start.utcoffset() == timedelta(hours=11) icalendar-6.3.1/src/icalendar/tests/test_issue_27_period.py000066400000000000000000000012101501302773300237240ustar00rootroot00000000000000"""Issue #27 - multiple periods https://github.com/collective/icalendar/issues/27 """ def test_issue_27_multiple_periods(calendars): free_busy = list( calendars.issue_27_multiple_periods_in_freebusy_multiple_freebusies.walk( "VFREEBUSY" ) )[0] free_busy_period = free_busy["freebusy"] print(free_busy["freebusy"]) equivalent_way_of_defining_free_busy = list( calendars.issue_27_multiple_periods_in_freebusy_one_freebusy.walk("VFREEBUSY") )[0] free_busy_period_equivalent = equivalent_way_of_defining_free_busy["freebusy"] assert free_busy_period == free_busy_period_equivalent icalendar-6.3.1/src/icalendar/tests/test_issue_301_add_rrule_as_string.py000066400000000000000000000006321501302773300265360ustar00rootroot00000000000000"""We want to add rrule as a string for convenience. See https://github.com/collective/icalendar/issues/301 """ from icalendar.cal import Event def test_rrule_add_example(): event = Event() event.add("RRULE", "FREQ=DAILY;INTERVAL=2") assert "FREQ=DAILY;INTERVAL=2" in event.to_ical().decode("utf-8") assert event.rrules[0]["freq"] == ["DAILY"] assert event.rrules[0]["interval"] == [2] icalendar-6.3.1/src/icalendar/tests/test_issue_318_skip_default_parameters.py000066400000000000000000000012421501302773300274270ustar00rootroot00000000000000"""Some parameters are specified as default by the RFC 5545. These tests make sure that these parameter values are not added to the properties. Example: DTSTART;VALUE=DATE-TIME:20190616T050000Z equals DTSTART:20190616T050000Z """ from datetime import datetime import pytest from icalendar import Event @pytest.mark.parametrize( "attr", [ "DTSTART", "DTEND", "DTSTAMP", ], ) def test_datetime_in_event(attr): """Check that the "VALUE=DATE-TIME" is absent because not needed.""" event = Event() event.add(attr, datetime(2022, 10, 13, 9, 16, 42)) ics = event.to_ical() assert b"VALUE=DATE-TIME" not in ics test_issue_322_single_strings_characters_split_into_multiple_categories.py000066400000000000000000000006101501302773300362610ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/testsfrom icalendar import Calendar, Event def test_issue_322_single_string_split_into_multiple_categories(calendars): calendar = Calendar() event = Event() event.add("summary", "Event with bare string as argument for categories") event.add("categories", "Lecture") calendar.add_component(event) assert calendar.to_ical() == calendars.issue_322_expected_calendar.raw_ics icalendar-6.3.1/src/icalendar/tests/test_issue_336_dateutil_timezone.py000066400000000000000000000015351501302773300262640ustar00rootroot00000000000000"""We would like to be able to get the right timezone names. See https://github.com/collective/icalendar/issues/336 It appears that the timezone Brazil/DeNoronha is actually America/Noronha. """ from datetime import datetime from dateutil import tz from icalendar import Event from icalendar.timezone import tzid_from_tzinfo valid_names = ("America/Noronha", "Brazil/DeNoronha") def test_timezone_name_directly(): """Try to get the name directly.""" tzinfo = tz.gettz("Brazil/DeNoronha") assert tzid_from_tzinfo(tzinfo) in valid_names def test_in_event(): """The example has an event in it and we want to have the id in it.""" event = Event() event.start = datetime(2025, 5, 13, 14, 23, tzinfo=tz.gettz("Brazil/DeNoronha")) ics = event.to_ical().decode() print(ics) assert any(name in ics for name in valid_names) icalendar-6.3.1/src/icalendar/tests/test_issue_348_exception_parsing_value.py000066400000000000000000000014751501302773300274620ustar00rootroot00000000000000"""These are tests for Issue #348 see https://github.com/collective/icalendar/issues/348 """ def test_calendar_can_be_parsed_correctly(calendars): """Exception when there's no ':' when parsing value #348 see https://github.com/collective/icalendar/issues/348 """ freebusy = calendars.issue_348_exception_parsing_value.walk("VFREEBUSY")[0] assert freebusy["ORGANIZER"].params["CN"] == "Sixt SE" def test_parameters_are_not_truncated(calendars): """We skip to the end and we do not want to loose parameters. see https://github.com/collective/icalendar/pull/514#issuecomment-1505878801 """ freebusy = calendars.issue_348_exception_parsing_value.walk("VFREEBUSY")[0] assert freebusy["X-ORGANIZER2"].params["CN"] == "Sixt SE" assert freebusy["X-ORGANIZER2"].params["CN2"] == "Test!" icalendar-6.3.1/src/icalendar/tests/test_issue_350.py000066400000000000000000000004361501302773300224520ustar00rootroot00000000000000"""Issue #350 - Ignore X-... properties also at end of file? https://github.com/collective/icalendar/issues/350 """ def test_issue_350(calendars): calendar = list(calendars.issue_350.walk("X-COMMENT")) assert len(calendar) == 0, "X-COMMENT at the end of the file was parsed" icalendar-6.3.1/src/icalendar/tests/test_issue_500_vboolean_for_parameter.py000066400000000000000000000006131501302773300272370ustar00rootroot00000000000000from icalendar import Event, vBoolean, vCalAddress def test_vBoolean_can_be_used_as_parameter_issue_500(events): """https://github.com/collective/icalendar/issues/500""" attendee = vCalAddress("mailto:someone@example.com") attendee.params["rsvp"] = vBoolean(True) event = Event() event.add("attendee", attendee) assert event.to_ical() == events.event_with_rsvp.raw_ics icalendar-6.3.1/src/icalendar/tests/test_issue_557_encode_native_parameters.py000066400000000000000000000117051501302773300275720ustar00rootroot00000000000000"""These are tests for Issue #557 TL;DR: Component._encode lost given parameters if the object to encode was already of native type, making its behavior unexpected. see https://github.com/collective/icalendar/issues/557""" import unittest from icalendar.cal import Component class TestComponentEncode(unittest.TestCase): def test_encode_non_native_parameters(self): """Test _encode to add parameters to non-natives""" self.__assert_native_content(self.summary) self.__assert_native_kept_parameters(self.summary) def test_encode_native_keep_params_None(self): """_encode should keep parameters on natives if parameters=None """ new_sum = self.__add_params( self.summary, parameters=None, ) self.__assert_native_content(new_sum) self.__assert_native_kept_parameters(new_sum) def test_encode_native_keep_params_empty(self): """_encode should keep paramters on natives if parameters={} """ new_sum = self.__add_params( self.summary, parameters={}, ) self.__assert_native_content(new_sum) self.__assert_native_kept_parameters(new_sum) def test_encode_native_append_params(self): """_encode should append paramters on natives keeping old parameters """ new_sum = self.__add_params( self.summary, parameters={"X-PARAM": "Test123"}, ) self.__assert_native_content(new_sum) self.__assert_native_kept_parameters(new_sum) self.assertParameter(new_sum, "X-PARAM", "Test123") def test_encode_native_overwrite_params(self): """_encode should overwrite single parameters if they have the same name as old ones""" new_sum = self.__add_params( self.summary, parameters={"LANGUAGE": "de"}, ) self.__assert_native_content(new_sum) self.assertParameter(new_sum, "LANGUAGE", "de") def test_encode_native_remove_params(self): """_encode should remove single parameters if they are explicitly set to None""" new_sum = self.__add_params( self.summary, parameters={"LANGUAGE": None}, ) self.__assert_native_content(new_sum) self.assertParameterMissing(new_sum, "LANGUAGE") def test_encode_native_remove_already_missing(self): """_encode should ignore removing a parameter that was already missing""" self.assertParameterMissing(self.summary, "X-MISSING") new_sum = self.__add_params( self.summary, parameters={"X-MISSING": None}, ) self.__assert_native_content(new_sum) self.__assert_native_kept_parameters(new_sum) self.assertParameterMissing(self.summary, "X-MISSING") def test_encode_native_full_test(self): """full test case with keeping, overwriting & removing properties""" # preperation orig_sum = self.__add_params( self.summary, parameters={ "X-OVERWRITE": "overwrite me!", "X-REMOVE": "remove me!", "X-MISSING": None, }, ) # preperation check self.__assert_native_content(orig_sum) self.__assert_native_kept_parameters(orig_sum) self.assertParameter(orig_sum, "X-OVERWRITE", "overwrite me!") self.assertParameter(orig_sum, "X-REMOVE", "remove me!") self.assertParameterMissing(orig_sum, "X-MISSING") # modification new_sum = self.__add_params( orig_sum, parameters={ "X-OVERWRITE": "overwritten", "X-REMOVE": None, "X-MISSING": None, }, ) # final asserts self.__assert_native_content(new_sum) self.__assert_native_kept_parameters(new_sum) self.assertParameter(new_sum, "X-OVERWRITE", "overwritten") self.assertParameterMissing(new_sum, "X-REMOVE") self.assertParameterMissing(new_sum, "X-MISSING") def setUp(self): self.summary = self.__gen_native() def __assert_native_kept_parameters(self, obj): self.assertParameter(obj, "LANGUAGE", "en") def __assert_native_content(self, obj): self.assertEqual(obj, "English Summary") def __add_params(self, obj, parameters): return Component._encode( "SUMMARY", obj, parameters=parameters, encode=True, ) def __gen_native(self): return Component._encode( "SUMMARY", "English Summary", parameters={ "LANGUAGE": "en", }, encode=True, ) def assertParameterMissing(self, obj, name): self.assertNotIn(name, obj.params) def assertParameter(self, obj, name, val): self.assertIn(name, obj.params) self.assertEqual(obj.params[name], val) icalendar-6.3.1/src/icalendar/tests/test_issue_662_component_properties.py000066400000000000000000000517661501302773300270320ustar00rootroot00000000000000"""This tests the properties of components and their types.""" from __future__ import annotations from datetime import date, datetime, timedelta import pytest from icalendar.error import IncompleteComponent, InvalidCalendar from icalendar.cal import Alarm try: from zoneinfo import ZoneInfo except ImportError: from backports.zoneinfo import ZoneInfo # type: ignore PGH003 from icalendar import ( Event, Journal, Todo, vDDDTypes, vDatetime, ) from icalendar.prop import vDuration def prop(component: Event | Todo, prop: str) -> str: """Translate the end property. This allows us to run the same tests on Event and Todo. """ if isinstance(component, Todo) and prop.upper() == "DTEND": return "DUE" return prop @pytest.fixture(params=[Event, Todo]) def start_end_component(request): """The event to test.""" return request.param() @pytest.fixture( params=[ datetime(2022, 7, 22, 12, 7), date(2022, 7, 22), datetime(2022, 7, 22, 13, 7, tzinfo=ZoneInfo("Europe/Paris")), ] ) def dtstart(request, set_component_start, start_end_component): """Start of the event.""" set_component_start(start_end_component, request.param) return request.param def _set_component_start_init(component, start): """Create the event with the __init__ method.""" d = dict(component) d["dtstart"] = vDDDTypes(start) component.clear() component.update(type(component)(d)) def _set_component_dtstart(component, start): """Create the event with the dtstart property.""" component.DTSTART = start def _set_component_start_attr(component, start): """Create the event with the dtstart property.""" component.start = start def _set_component_start_ics(component, start): """Create the event with the start property.""" component.add("dtstart", start) ics = component.to_ical().decode() print(ics) component.clear() component.update(type(component).from_ical(ics)) @pytest.fixture( params=[ _set_component_start_init, _set_component_start_ics, _set_component_dtstart, _set_component_start_attr, ] ) def set_component_start(request): """Create a new event.""" return request.param def test_component_dtstart(dtstart, start_end_component): """Test the start of events.""" assert start_end_component.DTSTART == dtstart def test_event_start(dtstart, start_end_component): """Test the start of events.""" assert start_end_component.start == dtstart invalid_start_event_1 = Event() invalid_start_event_1.add("dtstart", datetime(2022, 7, 22, 12, 7)) invalid_start_event_1.add("dtstart", datetime(2022, 7, 22, 12, 8)) invalid_start_event_2 = Event.from_ical(invalid_start_event_1.to_ical()) invalid_start_event_3 = Event() invalid_start_event_3.add("DTSTART", (date(2018, 1, 1), date(2018, 2, 1))) invalid_start_todo_1 = Todo(invalid_start_event_1) invalid_start_todo_2 = Todo(invalid_start_event_2) invalid_start_todo_3 = Todo(invalid_start_event_3) @pytest.mark.parametrize( "invalid_event", [ invalid_start_event_1, invalid_start_event_2, invalid_start_event_3, invalid_start_todo_1, invalid_start_todo_2, invalid_start_todo_3, ], ) def test_multiple_dtstart(invalid_event): """Check that we get the right error.""" with pytest.raises(InvalidCalendar): invalid_event.start # noqa: B018 with pytest.raises(InvalidCalendar): invalid_event.DTSTART # noqa: B018 def test_no_dtstart(start_end_component): """DTSTART is optional. The following is REQUIRED if the component appears in an iCalendar object that doesn't specify the "METHOD" property; otherwise, it is OPTIONAL; in any case, it MUST NOT occur more than once. """ assert start_end_component.DTSTART is None with pytest.raises(IncompleteComponent): start_end_component.start # noqa: B018 @pytest.fixture( params=[ datetime(2022, 7, 22, 12, 8), date(2022, 7, 23), datetime(2022, 7, 22, 14, 7, tzinfo=ZoneInfo("Europe/Paris")), ] ) def dtend(request, set_component_end, start_end_component): """end of the event.""" set_component_end(start_end_component, request.param) return request.param def _set_component_end_init(component, end): """Create the event with the __init__ method.""" d = dict(component) d[prop(component, "dtend")] = vDDDTypes(end) component.clear() component.update(type(component)(d)) def _set_component_end_property(component, end): """Create the event with the dtend property.""" setattr(component, prop(component, "DTEND"), end) def _set_component_end_attr(component, end): """Create the event with the dtend property.""" component.end = end def _set_component_end_ics(component, end): """Create the event with the end property.""" component.add(prop(component, "DTEND"), end) ics = component.to_ical().decode() print(ics) component.clear() component.update(type(component).from_ical(ics)) @pytest.fixture( params=[ _set_component_end_init, _set_component_end_ics, _set_component_end_property, _set_component_end_attr, ] ) def set_component_end(request): """Create a new event.""" return request.param def test_component_end_property(dtend, start_end_component): """Test the end of events.""" attr = prop(start_end_component, "DTEND") assert getattr(start_end_component, attr) == dtend # noqa: SIM300 def test_component_end(dtend, start_end_component): """Test the end of events.""" assert start_end_component.end == dtend @pytest.mark.parametrize("attr", ["DTSTART", "DTEND"]) def test_delete_attr(start_end_component, dtstart, dtend, attr): attr = prop(start_end_component, attr) delattr(start_end_component, attr) assert getattr(start_end_component, attr) is None delattr(start_end_component, attr) def _set_duration_vdddtypes(event: Event, duration: timedelta): """Set the vDDDTypes value""" event["DURATION"] = vDDDTypes(duration) def _set_duration_add(event: Event, duration: timedelta): """Set the vDDDTypes value""" event.add("DURATION", duration) def _set_duration_vduration(event: Event, duration: timedelta): """Set the vDDDTypes value""" event["DURATION"] = vDuration(duration) @pytest.fixture( params=[_set_duration_vdddtypes, _set_duration_add, _set_duration_vduration] ) def duration(start_end_component, dtstart, request): """... events have a DATE value type for the "DTSTART" property ... If such a "VEVENT" has a "DURATION" property, it MUST be specified as a "dur-day" or "dur-week" value. """ duration = ( timedelta(hours=1) if isinstance(dtstart, datetime) else timedelta(days=2) ) request.param(start_end_component, duration) return duration def test_start_and_duration(start_end_component, dtstart, duration): """Check calculation of end with duration.""" dur = start_end_component.end - start_end_component.start assert dur == duration assert start_end_component.duration == duration # The "VEVENT" is also the calendar component used to specify an # anniversary or daily reminder within a calendar. These events # have a DATE value type for the "DTSTART" property instead of the # default value type of DATE-TIME. If such a "VEVENT" has a "DTEND" # property, it MUST be specified as a DATE value also. invalid_event_end_1 = Event() invalid_event_end_1.add("DTSTART", datetime(2024, 1, 1, 10, 20)) invalid_event_end_1.add("DTEND", date(2024, 1, 1)) invalid_event_end_2 = Event() invalid_event_end_2.add("DTEND", datetime(2024, 1, 1, 10, 20)) invalid_event_end_2.add("DTSTART", date(2024, 1, 1)) invalid_event_end_3 = Event() invalid_event_end_3.add("DTEND", datetime(2024, 1, 1, 10, 20)) invalid_event_end_3.add("DTSTART", datetime(2024, 1, 1, 10, 20)) invalid_event_end_3.add("DURATION", timedelta(days=1)) invalid_event_end_4 = Event() invalid_event_end_4.add("DTSTART", date(2024, 1, 1)) invalid_event_end_4.add("DURATION", timedelta(hours=1)) invalid_todo_end_1 = Todo() invalid_todo_end_1.add("DTSTART", datetime(2024, 1, 1, 10, 20)) invalid_todo_end_1.add("DUE", date(2024, 1, 1)) invalid_todo_end_2 = Todo() invalid_todo_end_2.add("DUE", datetime(2024, 1, 1, 10, 20)) invalid_todo_end_2.add("DTSTART", date(2024, 1, 1)) invalid_todo_end_3 = Todo() invalid_todo_end_3.add("DUE", datetime(2024, 1, 1, 10, 20)) invalid_todo_end_3.add("DTSTART", datetime(2024, 1, 1, 10, 20)) invalid_todo_end_3.add("DURATION", timedelta(days=1)) invalid_todo_end_4 = Todo() invalid_todo_end_4.add("DTSTART", date(2024, 1, 1)) invalid_todo_end_4.add("DURATION", timedelta(hours=1)) @pytest.mark.parametrize( ("invalid_component", "message"), [ ( invalid_event_end_1, "DTSTART and DTEND must be of the same type, either date or datetime.", ), ( invalid_event_end_2, "DTSTART and DTEND must be of the same type, either date or datetime.", ), ( invalid_event_end_3, "Only one of DTEND and DURATION may be in a VEVENT, not both.", ), ( invalid_event_end_4, "When DTSTART is a date, DURATION must be of days or weeks.", ), ( invalid_todo_end_1, "DTSTART and DUE must be of the same type, either date or datetime.", ), ( invalid_todo_end_2, "DTSTART and DUE must be of the same type, either date or datetime.", ), ( invalid_todo_end_3, "Only one of DUE and DURATION may be in a VTODO, not both.", ), ( invalid_todo_end_4, "When DTSTART is a date, DURATION must be of days or weeks.", ), ], ) @pytest.mark.parametrize("attr", ["start", "end"]) def test_invalid_event(invalid_component, message, attr): """Test that the end and start throuw the right error.""" with pytest.raises(InvalidCalendar) as e: getattr(invalid_component, attr) assert e.value.args[0] == message def test_event_duration_zero(): """ For cases where a "VEVENT" calendar component specifies a "DTSTART" property with a DATE-TIME value type but no "DTEND" property, the event ends on the same calendar date and time of day specified by the "DTSTART" property. """ event = Event() event.start = datetime(2024, 10, 11, 10, 20) assert event.end == event.start assert event.duration == timedelta(days=0) def test_event_duration_one_day(): """ For cases where a "VEVENT" calendar component specifies a "DTSTART" property with a DATE value type but no "DTEND" nor "DURATION" property, the event's duration is taken to be one day """ event = Event() event.start = date(2024, 10, 11) assert event.end == event.start + timedelta(days=1) assert event.duration == timedelta(days=1) def test_todo_duration_zero(): """We do not know about the duration of a todo really.""" todo = Todo() todo.start = datetime(2024, 10, 11, 10, 20) assert todo.end == todo.start assert todo.duration == timedelta(days=0) def test_todo_duration_one_day(): """The end is at the end of the day, excluding midnight. RFC 5545: The following is an example of a "VTODO" calendar component that needs to be completed before May 1st, 2007. On midnight May 1st, 2007 this to-do would be considered overdue. """ event = Event() event.start = date(2024, 10, 11) assert event.end == event.start + timedelta(days=1) assert event.duration == timedelta(days=1) incomplete_event_1 = Event() incomplete_event_2 = Event() incomplete_event_2.add("DURATION", timedelta(hours=1)) incomplete_todo_1 = Todo() incomplete_todo_2 = Todo() incomplete_todo_2.add("DURATION", timedelta(hours=1)) @pytest.mark.parametrize( "incomplete_event_end", [ incomplete_event_1, incomplete_event_2, incomplete_todo_1, incomplete_todo_2, ], ) @pytest.mark.parametrize("attr", ["start", "end", "duration"]) def test_incomplete_event(incomplete_event_end, attr): """Test that the end throws the right error.""" with pytest.raises(IncompleteComponent): getattr(incomplete_event_end, attr) @pytest.mark.parametrize( "invalid_value", [ object(), timedelta(days=1), (datetime(2024, 10, 11, 10, 20), timedelta(days=1)), ], ) @pytest.mark.parametrize( ("Component", "attr"), [ (Event, "start"), (Event, "end"), (Event, "DTSTART"), (Event, "DTEND"), (Journal, "start"), (Journal, "end"), (Journal, "DTSTART"), (Todo, "start"), (Todo, "end"), (Todo, "DTSTART"), (Todo, "DUE"), ], ) def test_set_invalid_start(invalid_value, attr, Component): """Check that we get the right error. - other types that vDDDTypes accepts - object """ component = Component() with pytest.raises(TypeError) as e: setattr(component, attr, invalid_value) assert ( e.value.args[0] == f"Use datetime or date, not {type(invalid_value).__name__}." ) def setitem(d: dict, key, value): d[key] = value @pytest.mark.parametrize( "invalid_value", [ object(), None, (datetime(2024, 10, 11, 10, 20), timedelta(days=1)), date(2012, 2, 2), datetime(2022, 2, 2), ], ) def test_check_invalid_duration(start_end_component, invalid_value): """Check that we get the right error.""" start_end_component["DURATION"] = invalid_value with pytest.raises(InvalidCalendar) as e: start_end_component.DURATION # noqa: B018 assert ( e.value.args[0] == f"DURATION must be a timedelta, not {type(invalid_value).__name__}." ) def test_setting_the_end_deletes_the_duration(start_end_component): """Setting the end should not break the event.""" DTEND = prop(start_end_component, "DTEND") start_end_component.DTSTART = datetime(2024, 10, 11, 10, 20) start_end_component.DURATION = timedelta(days=1) setattr(start_end_component, DTEND, datetime(2024, 10, 11, 10, 21)) assert "DURATION" not in start_end_component assert start_end_component.DURATION is None end = getattr(start_end_component, DTEND) assert end == datetime(2024, 10, 11, 10, 21) def test_setting_duration_deletes_the_end(start_end_component): """Setting the duration should not break the event.""" DTEND = prop(start_end_component, "DTEND") start_end_component.DTSTART = datetime(2024, 10, 11, 10, 20) setattr(start_end_component, DTEND, datetime(2024, 10, 11, 10, 21)) start_end_component.DURATION = timedelta(days=1) assert DTEND not in start_end_component assert getattr(start_end_component, DTEND) is None assert start_end_component.DURATION == timedelta(days=1) valid_values = pytest.mark.parametrize( ("attr", "value"), [ ("DTSTART", datetime(2024, 10, 11, 10, 20)), ("DTEND", datetime(2024, 10, 11, 10, 20)), ("DURATION", timedelta(days=1)), ], ) @valid_values def test_setting_to_none_deletes_value(start_end_component, attr, value): """Setting attributes to None deletes them.""" attr = prop(start_end_component, attr) setattr(start_end_component, attr, value) assert attr in start_end_component assert getattr(start_end_component, attr) == value setattr(start_end_component, attr, None) assert attr not in start_end_component @valid_values def test_setting_a_value_twice(start_end_component, attr, value): """Setting attributes twice replaces them.""" attr = prop(start_end_component, attr) setattr(start_end_component, attr, value + timedelta(days=1)) setattr(start_end_component, attr, value) assert getattr(start_end_component, attr) == value @pytest.mark.parametrize("attr", ["DTSTART", "DTEND", "DURATION"]) def test_invalid_none(start_end_component, attr): """Special case for None.""" attr = prop(start_end_component, attr) start_end_component[attr] = None with pytest.raises(InvalidCalendar): getattr(start_end_component, attr) def test_delete_duration(start_end_component): """Test the del command.""" start_end_component.DURATION = timedelta(days=1) del start_end_component.DURATION assert start_end_component.DURATION is None @pytest.mark.parametrize("attr", ["DTSTART", "end", "start"]) @pytest.mark.parametrize( "start", [ datetime(2024, 10, 11, 10, 20), date(2024, 10, 11), datetime(2024, 10, 11, 10, 20, tzinfo=ZoneInfo("Europe/Paris")), ], ) def test_journal_start(start, attr): """Test that we can set the start of a journal.""" j = Journal() setattr(j, attr, start) assert start == j.DTSTART assert j.start == start assert j.end == start assert j.duration == timedelta(0) @pytest.mark.parametrize("attr", ["start", "end"]) def test_delete_journal_start(attr): """Delete the start of the journal.""" j = Journal() j.start = datetime(2010, 11, 12, 13, 14) j.DTSTART = None assert j.DTSTART is None assert "DTSTART" not in j with pytest.raises(IncompleteComponent): getattr(j, attr) def setting_twice_does_not_duplicate_the_entry(): j = Journal() j.DTSTART = date(2024, 1, 1) j.DTSTART = date(2024, 1, 3) assert date(2024, 1, 3) == j.DTSTART assert j.start == date(2024, 1, 3) assert j.end == date(2024, 1, 3) @pytest.mark.parametrize( ("file", "trigger", "related"), [ ( "rfc_5545_absolute_alarm_example", vDatetime.from_ical("19970317T133000Z"), "START", ), ("rfc_5545_end", timedelta(days=-2), "END"), ("start_date", timedelta(days=-2), "START"), ], ) def test_get_alarm_trigger_property(alarms, file, trigger, related): """Get the trigger property.""" alarm = alarms[file] assert alarm.TRIGGER == trigger assert alarm.TRIGGER_RELATED == related def test_set_alarm_trigger(): """Set the alarm trigger.""" a = Alarm() a.TRIGGER = timedelta(hours=1) assert a.TRIGGER == timedelta(hours=1) assert a.TRIGGER_RELATED == "START" def test_set_alarm_trigger_related(): """Set the alarm trigger.""" a = Alarm() a.TRIGGER = timedelta(hours=1) a.TRIGGER_RELATED = "END" assert a.TRIGGER == timedelta(hours=1) assert a.TRIGGER_RELATED == "END" def test_get_related_without_trigger(): """The default is start""" assert Alarm().TRIGGER_RELATED == "START" def test_cannot_set_related_without_trigger(): """TRIGGER must be set to set the parameter.""" with pytest.raises(ValueError) as e: a = Alarm() a.TRIGGER_RELATED = "END" assert ( e.value.args[0] == "You must set a TRIGGER before setting the RELATED parameter." ) @pytest.mark.parametrize( ("file", "triggers"), [ ( "rfc_5545_absolute_alarm_example", ( (), (), ( vDatetime.from_ical("19970317T133000Z"), vDatetime.from_ical("19970317T134500Z"), vDatetime.from_ical("19970317T140000Z"), vDatetime.from_ical("19970317T141500Z"), vDatetime.from_ical("19970317T143000Z"), ), ), ), ("rfc_5545_end", ((), (timedelta(days=-2),), ())), ("start_date", ((timedelta(days=-2),), (), ())), ], ) def test_get_alarm_triggers(alarms, file, triggers): """Get the trigger property.""" alarm = alarms[file] print(tuple(alarm.triggers)) print(triggers) assert alarm.triggers == triggers def test_triggers_emtpy_alarm(): """An alarm with no trigger has no triggers.""" assert Alarm().triggers == ((), (), ()) h1 = timedelta(hours=1) def test_triggers_emtpy_with_no_repeat(): """Check incomplete values.""" a = Alarm() a.TRIGGER = h1 a.DURATION = h1 assert a.triggers == ((h1,), (), ()) def test_triggers_emtpy_with_no_duration(): """Check incomplete values.""" a = Alarm() a.TRIGGER = h1 a.REPEAT = 10 assert a.triggers == ((h1,), (), ()) @pytest.mark.parametrize( ("file", "triggers"), [ ( "rfc_5545_absolute_alarm_example", ((), (), (vDatetime.from_ical("19970317T133000Z"),)), ), ("rfc_5545_end", ((), (timedelta(days=-2),), ())), ("start_date", ((timedelta(days=-2),), (), ())), ], ) @pytest.mark.parametrize("duration", [timedelta(days=-1), h1]) @pytest.mark.parametrize("repeat", [1, 3]) def test_get_alarm_triggers_repeated(alarms, file, triggers, duration, repeat): """Get the trigger property.""" alarm = alarms[file].copy() alarm.REPEAT = repeat alarm.DURATION = duration for expected, triggers in zip(triggers, alarm.triggers): if not expected: assert triggers == () continue assert len(triggers) == 1 + repeat assert triggers[0] == expected[0] for x, y in zip(triggers[:-1], triggers[1:]): assert y - x == duration icalendar-6.3.1/src/icalendar/tests/test_issue_716_alarm_time_computation.py000066400000000000000000000356001501302773300272750ustar00rootroot00000000000000"""Test the alarm time computation. Events can have alarms. Alarms can be in this state: - active - the user wants the alarm to pop up - acknowledged - the user no longer wants the alarm - snoozed - the user moved that alarm to another time The alarms can only work on the properties of the event like DTSTART, DTEND, and DURATION. """ from datetime import date, datetime, timedelta, timezone import pytest from icalendar import Event from icalendar.alarms import Alarms from icalendar.cal import Alarm from icalendar.error import IncompleteAlarmInformation from icalendar.prop import vDatetime from icalendar.tools import normalize_pytz UTC = timezone.utc EXAMPLE_TRIGGER = datetime(1997, 3, 17, 13, 30, tzinfo=UTC) def test_absolute_alarm_time_rfc_example(alarms): """Check that the absolute alarm is recognized. The following example is for a "VALARM" calendar component that specifies an audio alarm that will sound at a precise time and repeat 4 more times at 15-minute intervals: """ a = Alarms(alarms.rfc_5545_absolute_alarm_example) times = a.times assert len(times) == 5 for i, t in enumerate(times): assert t.trigger == EXAMPLE_TRIGGER + timedelta(minutes=15 * i) alarm_1 = Alarm() alarm_1.add("TRIGGER", EXAMPLE_TRIGGER) alarm_2 = Alarm() alarm_2["TRIGGER"] = vDatetime(EXAMPLE_TRIGGER) @pytest.mark.parametrize("alarm", [alarm_1, alarm_2]) def test_absolute_alarm_time_with_vDatetime(alarm): """Check that the absolute alarm is recognized. The following example is for a "VALARM" calendar component that specifies an audio alarm that will sound at a precise time and repeat 4 more times at 15-minute intervals: """ a = Alarms(alarm) times = a.times assert len(times) == 1 assert times[0].trigger == EXAMPLE_TRIGGER alarm_incomplete_1 = Alarm() alarm_incomplete_1.TRIGGER = timedelta(hours=2) alarm_incomplete_1.DURATION = timedelta(hours=1) alarm_incomplete_2 = Alarm() alarm_incomplete_2.TRIGGER = timedelta(hours=2) alarm_incomplete_2.REPEAT = 100 @pytest.mark.parametrize("alarm", [alarm_incomplete_1, alarm_incomplete_2]) def test_alarm_has_only_one_of_repeat_or_duration(alarm): """This is an edge case and we should ignore the repetition.""" a = Alarms(alarm) a.set_start(datetime(2027, 12, 2)) assert len(a.times) == 1 @pytest.fixture(params=[(0, timedelta(minutes=-30)), (1, timedelta(minutes=-25))]) def alarm_before_start(calendars, request): """An example alarm relative to the start of a component.""" index, td = request.param alarm = calendars.alarm_etar_future.subcomponents[-1].subcomponents[index] assert isinstance(alarm, Alarm) assert alarm.get("TRIGGER").dt == td alarm.test_td = td return alarm def test_cannot_compute_relative_alarm_without_start(alarm_before_start): """We have an alarm without a start of a component.""" with pytest.raises(IncompleteAlarmInformation) as e: Alarms(alarm_before_start).times # noqa: B018 assert ( e.value.args[0] == f"Use {Alarms.__name__}.{Alarms.set_start.__name__} because at least one alarm is relative to the start of a component." ) @pytest.mark.parametrize( ("dtstart", "timezone", "trigger"), [ ( datetime(2024, 10, 29, 13, 10), "UTC", datetime(2024, 10, 29, 13, 10, tzinfo=UTC), ), (date(2024, 11, 16), None, datetime(2024, 11, 16, 0, 0)), ( datetime(2024, 10, 29, 13, 10), "Asia/Singapore", datetime(2024, 10, 29, 5, 10, tzinfo=UTC), ), (datetime(2024, 10, 29, 13, 20), None, datetime(2024, 10, 29, 13, 20)), ], ) def test_can_complete_relative_calculation_if_a_start_is_given( alarm_before_start, dtstart, timezone, trigger, tzp ): """The start is given and required.""" start = dtstart if timezone is None else tzp.localize(dtstart, timezone) alarms = Alarms(alarm_before_start) alarms.set_start(start) assert len(alarms.times) == 1 time = alarms.times[0] expected_trigger = normalize_pytz(trigger + alarm_before_start.test_td) assert time.trigger == expected_trigger @pytest.mark.parametrize("dtstart", [date(1998, 10, 1), date(2023, 12, 31)]) def test_start_as_date_with_delta_as_date_stays_date(alarms, dtstart): """If we have an alarm with a day delta and the event is a day event, we should stay as a date.""" a = Alarms(alarms.start_date) a.set_start(dtstart) assert len(a.times) == 1 assert a.times[0].trigger == dtstart - timedelta(days=2) def test_cannot_compute_relative_alarm_without_end(alarms): """We have an alarm without an end of a component.""" with pytest.raises(IncompleteAlarmInformation) as e: Alarms(alarms.rfc_5545_end).times # noqa: B018 assert ( e.value.args[0] == f"Use {Alarms.__name__}.{Alarms.set_end.__name__} because at least one alarm is relative to the end of a component." ) @pytest.mark.parametrize( ("dtend", "timezone", "trigger"), [ ( datetime(2024, 10, 29, 13, 10), "UTC", datetime(2024, 10, 29, 13, 10, tzinfo=UTC), ), (date(2024, 11, 16), None, date(2024, 11, 16)), ( datetime(2024, 10, 29, 13, 10), "Asia/Singapore", datetime(2024, 10, 29, 5, 10, tzinfo=UTC), ), (datetime(2024, 10, 29, 13, 20), None, datetime(2024, 10, 29, 13, 20)), ], ) def test_can_complete_relative_calculation(alarms, dtend, timezone, trigger, tzp): """The start is given and required.""" start = dtend if timezone is None else tzp.localize(dtend, timezone) alarms = Alarms(alarms.rfc_5545_end) alarms.set_end(start) assert len(alarms.times) == 1 time = alarms.times[0] expected_trigger = normalize_pytz(trigger - timedelta(days=2)) assert time.trigger == expected_trigger @pytest.mark.parametrize("dtend", [date(1998, 10, 1), date(2023, 12, 31)]) def test_end_as_date_with_delta_as_date_stays_date(alarms, dtend): """If we have an alarm with a day delta and the event is a day event, we should stay as a date.""" a = Alarms(alarms.rfc_5545_end) a.set_end(dtend) assert len(a.times) == 1 assert a.times[0].trigger == dtend - timedelta(days=2) def test_add_multiple_alarms(alarms): """We can add multiple alarms.""" a = Alarms() a.add_alarm(alarms.start_date) a.add_alarm(alarms.rfc_5545_end) a.add_alarm(alarms.rfc_5545_absolute_alarm_example) with pytest.raises(IncompleteAlarmInformation): a.times # noqa: B018 a.set_start(datetime(2012, 3, 5)) with pytest.raises(IncompleteAlarmInformation): a.times # noqa: B018 a.set_end(datetime(2012, 3, 5)) assert len(a.times) == 7 def test_alarms_from_event_have_right_times(calendars): """We can collect from an event.""" event = calendars.alarm_etar_future.subcomponents[-1] a = Alarms(event) assert len(a.times) == 3 assert a.times[0].parent == event def test_cannot_set_the_event_twice(calendars): """We cannot set an event twice. This make the state ambiguous.""" event = calendars.alarm_etar_future.subcomponents[-1] a = Alarms() a.add_component(event) a.add_component(event) # same component is ok with pytest.raises(ValueError): a.add_component(calendars.alarm_google_future.subcomponents[-1]) @pytest.mark.parametrize( ("calendar", "index", "count", "message"), [ ("alarm_etar_future", -1, 3, "Etar (1): we just created the alarm"), ("alarm_etar_notification", -1, 2, "Etar (2): the notification popped up"), ( "alarm_etar_notification_clicked", -1, 0, "Etar (3): the notification was dismissed", ), # TODO: check that that is really true ( "alarm_google_future", -1, 4, "Google (1): we just created the event with alarms", ), ( "alarm_google_acknowledged", -1, 2, "Google (2): 2 alarms happened at the same time", ), ("alarm_thunderbird_future", -1, 2, "Thunderbird (1.1): 2 alarms are set"), ( "alarm_thunderbird_snoozed_until_1457", -1, 2, "Thunderbird (1.2): 2 alarms are snoozed to another time", ), ( "alarm_thunderbird_closed", -1, 0, "Thunderbird (1.3): all alarms are dismissed (closed)", ), ("alarm_thunderbird_2_future", -1, 2, "Thunderbird (2.1): 2 alarms active"), ( "alarm_thunderbird_2_notification_popped_up", -1, 2, "Thunderbird (2.2): one alarm popped up as a notification", ), ( "alarm_thunderbird_2_notification_5_min_postponed", -1, 2, "Thunderbird (2.3): 1 alarm active and one postponed by 5 minutes", ), ( "alarm_thunderbird_2_notification_5_min_postponed_and_popped_up", -1, 2, "Thunderbird (2.4): 1 alarm active and one postponed by 5 minutes and now popped up", ), ( "alarm_thunderbird_2_notification_5_min_postponed_and_closed", -1, 1, "Thunderbird (2.5): 1 alarm active and one postponed by 5 minutes and is now acknowledged", ), ], ) def test_number_of_active_alarms_from_calendar_software( calendars, calendar, index, count, message ): """Check that we extract calculate the correct amount of active alarms.""" event = calendars[calendar].subcomponents[index] a = Alarms(event) active_alarms = ( a.active ) # We do not need to pass a timezone because the events have a timezone assert ( len(active_alarms) == count ), f"{message} - I expect {count} alarms active but got {len(active_alarms)}." three_alarms = Alarm() three_alarms.REPEAT = 2 three_alarms.add("DURATION", timedelta(hours=1)) # 2 hours & 1 hour before three_alarms.add("TRIGGER", -timedelta(hours=3)) # 3 hours before @pytest.mark.parametrize( ("start", "acknowledged", "timezone", "count"), [ (datetime(2024, 10, 10), datetime(2024, 10, 9), "UTC", 3), (datetime(2024, 10, 10, 12), datetime(2024, 10, 10, 9, 1), "UTC", 2), (datetime(2024, 10, 10, 12), datetime(2024, 10, 10, 10, 1), "UTC", 1), (datetime(2024, 10, 10, 12), datetime(2024, 10, 10, 11, 1), "UTC", 0), ( datetime(2024, 10, 10, 12, tzinfo=timezone.utc), datetime(2024, 10, 10, 11, 1), None, 0, ), ], ) def test_number_of_active_alarms_with_moving_time( start, acknowledged, count, tzp, timezone ): """Check how many alarms are active after a time they are acknowledged.""" a = Alarms() a.add_alarm(three_alarms) a.set_start(start) a.set_local_timezone(timezone) a.acknowledge_until(tzp.localize_utc(acknowledged)) active = a.active assert len(active) == count def test_incomplete_alarm_information_for_active_state(tzp): """Make sure we throw the right error.""" a = Alarms() a.add_alarm(three_alarms) a.set_start(date(2017, 12, 1)) a.acknowledge_until(tzp.localize_utc(datetime(2012, 10, 10, 12))) with pytest.raises(IncompleteAlarmInformation) as e: a.active # noqa: B018 assert ( e.value.args[0] == f"A local timezone is required to check if the alarm is still active. Use Alarms.{Alarms.set_local_timezone.__name__}()." ) @pytest.mark.parametrize( "calendar_name", [ "alarm_etar_future", "alarm_google_acknowledged", "alarm_thunderbird_closed", "alarm_thunderbird_future", "alarm_thunderbird_snoozed_until_1457", ], ) def test_thunderbird_recognition(calendars, calendar_name): """Check if we correctly discover Thunderbird's alarm algorithm.""" calendar = calendars[calendar_name] event = calendar.subcomponents[-1] assert isinstance(event, Event) assert event.is_thunderbird() == ("thunderbird" in calendar_name) @pytest.mark.parametrize( "snooze", [ datetime(2012, 10, 10, 11, 1), # before everything datetime(2017, 12, 1, 10, 1), datetime(2017, 12, 1, 11, 1), datetime(2017, 12, 1, 12, 1), datetime(2017, 12, 1, 13, 1), # snooze until after the start of the event ], ) def test_snoozed_alarm_has_trigger_at_snooze_time(tzp, snooze): """When an alarm is snoozed, it pops up after the snooze time.""" a = Alarms() a.add_alarm(three_alarms) a.set_start(datetime(2017, 12, 1, 13)) a.set_local_timezone("UTC") snooze_utc = tzp.localize_utc(snooze) a.snooze_until(snooze_utc) active = a.active assert len(active) == 3 for alarm in active: assert alarm.trigger >= snooze_utc @pytest.mark.parametrize( ("event_index", "alarm_times"), [ # Assume that we have the following event with an alarm set to trigger 15 minutes before the meeting: (1, ("20210302T101500",)), # When the alarm is triggered, the user decides to "snooze" it for 5 minutes. # The client acknowledges the original alarm and creates a new "snooze" # alarm as a sibling of, and relates it to, the original alarm (note that # both occurrences of "VALARM" reside within the same "parent" VEVENT): (2, ("20210302T102000",)), # When the "snooze" alarm is triggered, the user decides to "snooze" it # again for an additional 5 minutes. The client once again acknowledges # the original alarm, removes the triggered "snooze" alarm, and creates another # new "snooze" alarm as a sibling of, and relates it to, the original alarm # (note the different UID for the new "snooze" alarm): (3, ("20210302T102500",)), # When the second "snooze" alarm is triggered, the user decides to dismiss it. # The client acknowledges both the original alarm and the second "snooze" alarm: (4, ()), ], ) def test_rfc_9074_alarm_times(events, event_index, alarm_times): """Test the examples from the RFC and their timing. Add times use America/New_York as timezone. """ a = events[f"rfc_9074_example_{event_index}"].alarms assert len(a.active) == len(alarm_times) expected_alarm_times = { vDatetime.from_ical(t, "America/New_York") for t in alarm_times } computed_alarm_times = {alarm.trigger for alarm in a.active} assert expected_alarm_times == computed_alarm_times def test_set_to_None(): """acknowledge_until, snooze_until, set_local_timezone.""" a = Alarms() a.set_start(None) a.set_end(None) a.set_local_timezone(None) a.acknowledge_until(None) a.snooze_until(None) assert vars(a) == vars(Alarms()) def test_delete_TRIGGER(): """Delete the TRIGGER property.""" a = Alarm() a.TRIGGER = datetime(2017, 12, 1, 10, 1) del a.TRIGGER assert a.TRIGGER is None icalendar-6.3.1/src/icalendar/tests/test_issue_720_uid_property.py000066400000000000000000000003651501302773300252610ustar00rootroot00000000000000"""This tests the UID property. See https://github.com/collective/icalendar/issues/740 """ from icalendar import Alarm def test_alarm_uses_x_property_too(): alarm = Alarm() alarm["X-ALARMUID"] = "1234" assert alarm.uid == "1234" icalendar-6.3.1/src/icalendar/tests/test_issue_722_generate_vtimezone.py000066400000000000000000000402101501302773300264210ustar00rootroot00000000000000"""Generate VTIMEZONE components from actual timezone information. When we generate VTIMEZONE from actual tzinfo instances of - dateutil - zoneinfo - pytz Then, we cannot assume that the future information stays the same but we should be able to create tests that work for the past. """ from datetime import date, datetime, timedelta from re import findall import pytest from dateutil.tz import gettz try: from zoneinfo import available_timezones except ImportError: from backports.zoneinfo import available_timezones from icalendar import Calendar, Component, Event, Timezone from icalendar.timezone import tzid_from_tzinfo, tzids_from_tzinfo tzids = pytest.mark.parametrize( "tzid", [ "Europe/Berlin", "Asia/Singapore", "America/New_York", ], ) def assert_components_equal(c1: Component, c2: Component): """Print the diff of two components.""" ML = 32 ll1 = c1.to_ical().decode().splitlines() ll2 = c2.to_ical().decode().splitlines() pad = max(len(l) for l in ll1 if len(l) <= ML) diff = 0 for l1, l2 in zip(ll1, ll2): a = len(l1) > 32 or len(l2) > 32 print( a * " " + l1, " " * (pad - len(l1)), a * "\n->" + l2, " " * (pad - len(l2)), "\tdiff!" if l1 != l2 else "", ) diff += l1 != l2 assert not diff, f"{diff} lines differ" @tzids def test_conversion_converges(tzp, tzid): """tzinfo -> VTIMEZONE -> tzinfo -> VTIMEZONE We can assume that both generated VTIMEZONEs are equivalent. """ if tzp.uses_pytz(): pytest.skip( "pytz will not converge on the first run. This is problematic. PYTZ-TODO" ) tzinfo1 = tzp.timezone(tzid) assert tzinfo1 is not None generated1 = Timezone.from_tzinfo(tzinfo1) generated1["TZID"] = ( "test-generated" # change the TZID so we do not use an existing one ) tzinfo2 = generated1.to_tz() generated2 = Timezone.from_tzinfo(tzinfo2, "test-generated") tzinfo3 = generated2.to_tz() generated3 = Timezone.from_tzinfo(tzinfo3, "test-generated") # pprint(generated1.get_transitions()) # pprint(generated2.get_transitions()) assert_components_equal(generated1, generated2) assert_components_equal(generated2, generated3) assert 2 <= len(generated1.standard + generated1.daylight) <= 3 assert 2 <= len(generated2.standard + generated2.daylight) <= 3 assert dict(generated1) == dict(generated2) assert generated1.to_ical().decode() == generated2.to_ical().decode() assert generated1.daylight == generated2.daylight assert generated1.standard == generated2.standard assert generated1 == generated2 @tzids def both_tzps_generate_the_same_info(tzid, tzp): """We want to make sure that we get the same info for all timezone implementations. We assume that - the timezone implementations have the same info within the days we test - the timezone transitions times do not change because they are before last_date """ # default generation tz1 = Timezone.from_tzid(tzid, tzp, last_date=date(2024, 1, 1)) tzp.use_zoneinfo() # we compare to zoneinfo tz2 = Timezone.from_tzid(tzid, tzp, last_date=date(2024, 1, 1)) assert_components_equal(tz1, tz2) assert tz1 == tz2 @tzids def test_tzid_matches(tzid, tzp): """Check the TZID.""" tz = Timezone.from_tzinfo(tzp.timezone(tzid)) assert tz["TZID"] == tzid def test_do_not_convert_utc(tzp): """We do not need to convert UTC but it should work.""" utc = Timezone.from_tzid("UTC") assert utc.daylight == [] assert len(utc.standard) == 1 standard = utc.standard[0] assert standard["TZOFFSETFROM"].td == timedelta(0) assert standard["TZOFFSETTO"].td == timedelta(0) assert standard["TZNAME"] == "UTC" def test_berlin_time(tzp): """Test the Europe/Berlin timezone conversion.""" tz = Timezone.from_tzid("Europe/Berlin") # we should have two timezones in it for x in tz.standard: print(x.name, x["TZNAME"], x["TZOFFSETFROM"].td, x["TZOFFSETTO"].td) print(x.to_ical().decode()) assert len(tz.daylight) == 1 assert len(tz.standard) in (1, 2), "We start in winter" dst = tz.daylight[-1] sta = tz.standard[-1] assert dst["TZNAME"] == "CEST" # summer assert sta["TZNAME"] == "CET" assert dst["TZOFFSETFROM"].td == timedelta(hours=1) # summer assert sta["TZOFFSETFROM"].td == timedelta(hours=2) assert dst["TZOFFSETTO"].td == timedelta(hours=2) # summer assert sta["TZOFFSETTO"].td == timedelta(hours=1) def test_range_is_not_crossed(): first_date = datetime(2023, 1, 1) last_date = datetime(2024, 1, 1) def check(dt): assert first_date <= dt <= last_date tz = Timezone.from_tzid("Europe/Berlin", last_date=last_date, first_date=first_date) for sub in tz.standard + tz.daylight: check(sub.DTSTART) for rdate in sub.get("RDATE", []): check(rdate) @tzids def test_use_the_original_timezone(tzid, tzp): """When we get the timezone again, usually, we should use the one of the library/tzp.""" tzinfo1 = tzp.timezone(tzid) assert tzinfo1 is not None generated1 = Timezone.from_tzinfo(tzinfo1) tzinfo2 = generated1.to_tz() assert type(tzinfo1) == type(tzinfo2) assert tzinfo1 == tzinfo2 @pytest.mark.parametrize( ("tzid", "dt", "tzname"), [ ("Asia/Singapore", datetime(1970, 1, 1), "+0730"), ("Asia/Singapore", datetime(1981, 12, 31), "+0730"), ("Asia/Singapore", datetime(1981, 12, 31, 23, 10), "+0730"), ("Asia/Singapore", datetime(1981, 12, 31, 23, 34), "+0730"), ("Asia/Singapore", datetime(1981, 12, 31, 23, 59, 59), "+0730"), ("Asia/Singapore", datetime(1982, 1, 1), "+08"), ("Asia/Singapore", datetime(1982, 1, 1, 0, 1), "+08"), ("Asia/Singapore", datetime(1982, 1, 1, 0, 34), "+08"), ("Asia/Singapore", datetime(1982, 1, 1, 1, 0), "+08"), ("Asia/Singapore", datetime(1982, 1, 1, 1, 1), "+08"), ("Europe/Berlin", datetime(1970, 1, 1), "CET"), ("Europe/Berlin", datetime(2024, 3, 31, 0, 0), "CET"), ("Europe/Berlin", datetime(2024, 3, 31, 1, 0), "CET"), ("Europe/Berlin", datetime(2024, 3, 31, 2, 0), "CET"), ("Europe/Berlin", datetime(2024, 3, 31, 2, 59, 59), "CET"), ("Europe/Berlin", datetime(2024, 3, 31, 3, 0), "CEST"), ("Europe/Berlin", datetime(2024, 3, 31, 3, 0, 1), "CEST"), ("Europe/Berlin", datetime(2024, 3, 31, 4, 0), "CEST"), ("Europe/Berlin", datetime(2024, 10, 27, 0, 0), "CEST"), ("Europe/Berlin", datetime(2024, 10, 27, 1, 0), "CEST"), ("Europe/Berlin", datetime(2024, 10, 27, 2, 0), "CEST"), ("Europe/Berlin", datetime(2024, 10, 27, 2, 30), "CEST"), ("Europe/Berlin", datetime(2024, 10, 27, 2, 59, 59), "CEST"), ("Europe/Berlin", datetime(2024, 10, 27, 3, 0), "CET"), ("Europe/Berlin", datetime(2024, 10, 27, 3, 0, 1), "CET"), ("Europe/Berlin", datetime(2024, 10, 27, 4, 0), "CET"), # transition times from https://www.zeitverschiebung.net/de/timezone/america--new_york ("America/New_York", datetime(1970, 1, 1), "EST"), # Daylight Saving Time ("America/New_York", datetime(2024, 11, 3, 0, 0), "EDT"), ("America/New_York", datetime(2024, 11, 3, 1, 0), "EDT"), ("America/New_York", datetime(2024, 11, 3, 1, 59, 59), "EDT"), # 03.11.2024 2:00am -> 1:00am Standard # ("America/New_York", datetime(2024, 11, 3, 2, 0), "EDT"), ("America/New_York", datetime(2024, 11, 3, 2, 0, 1), "EST"), ("America/New_York", datetime(2024, 11, 3, 3, 0), "EST"), ("America/New_York", datetime(2025, 3, 9, 1, 0), "EST"), ("America/New_York", datetime(2025, 3, 9, 1, 59, 59), "EST"), ("America/New_York", datetime(2025, 3, 9, 2, 0), "EST"), # 09.03.2025 2:00am -> 3:00am Daylight Saving Time ("America/New_York", datetime(2025, 3, 9, 3, 0), "EDT"), ("America/New_York", datetime(2025, 3, 9, 3, 1, 1), "EDT"), ("America/New_York", datetime(2025, 3, 9, 4, 0), "EDT"), ], ) def test_check_datetimes_around_transition_times(tzp, tzid, dt, tzname): """We should make sure than the datetimes with the generated timezones work as expected: They have the right UTC offset, dst and tzname. """ message = f"{tzid}: {dt} (expected in {tzname})" expected_dt = tzp.localize(dt, tzid) component = Timezone.from_tzinfo(tzp.timezone(tzid)) generated_tzinfo = component.to_tz(tzp, lookup_tzid=False) generated_dt = dt.replace(tzinfo=generated_tzinfo) print(generated_tzinfo) if tzp.uses_pytz(): # generated_dt = generated_tzinfo.localize(dt) generated_dt = generated_tzinfo.normalize(generated_dt) if dt in ( datetime(2024, 10, 27, 1, 0), datetime(2024, 11, 3, 1, 59, 59), datetime(2024, 11, 3, 1, 0), datetime(2024, 11, 3, 0, 0), datetime(2024, 10, 27, 2, 59, 59), datetime(2024, 10, 27, 2, 30), datetime(2024, 10, 27, 2, 0), datetime(2024, 10, 27, 1, 0), ): pytest.skip("We do not know how to do this. PYTZ-TODO") assert generated_dt.tzname() == expected_dt.tzname() == tzname, message assert generated_dt.dst() == expected_dt.dst(), message assert generated_dt.utcoffset() == expected_dt.utcoffset(), message @pytest.mark.parametrize("uid", [0, 1, 2, 3]) def test_dateutil_timezone_when_time_is_going_backwards(calendars, tzp, uid): """When going from Daylight Saving Time to Standard Time, times can be ambiguous. For example, at 3:00 AM, the time falls back to 2:00 AM, repeating a full hour of times from 2:00 AM to 3:00 AM on the same date. By the RFC 5545, we cannot accommodate this case. All datetimes should be BEFORE the transition if ambiguous. However, dateutil can create a timezone that allows the event to be after this ambiguous time span, of course. Each event has its timezone saved in it. """ cal: Calendar = calendars.issue_722_timezone_transition_ambiguity event: Event = cal.events[uid] expected_tzname = str(event["X-TZNAME"]) actual_tzname = event.start.tzname() assert actual_tzname == expected_tzname, event["SUMMARY"] def query_tzid(query: str, cal: Calendar) -> str: """The tzid from the query.""" try: tzinfo = eval(query, {"cal": cal}) # noqa: S307 except Exception as e: raise ValueError(query) from e return tzid_from_tzinfo(tzinfo) # these are queries for all the places that have a TZID # according to RFC 5545 queries = [ "cal.events[0].start.tzinfo", # DTSTART "cal.events[0].end.tzinfo", # DTEND # EXDATE "cal.todos[0].end.tzinfo", # DUE "cal.events[0].get('RDATE').dts[0].dt[0].tzinfo", # RDATE "cal.events[1].get('RECURRENCE-ID').dt.tzinfo", # RECURRENCE-ID "cal.events[2].get('RDATE')[0].dts[0].dt.tzinfo", # RDATE multiple "cal.events[2].get('RDATE')[1].dts[0].dt.tzinfo", # RDATE multiple ] @pytest.mark.parametrize("query", queries) def test_add_missing_timezones_to_example(calendars, query): """Add the missing timezones to the calendar.""" cal = calendars.issue_722_missing_timezones tzid = query_tzid(query, cal) tzs = cal.get_missing_tzids() assert tzid in tzs def test_queries_yield_unique_tzids(calendars): """We make sure each query tests a unique place to find for the algorithm.""" cal = calendars.issue_722_missing_timezones tzids = set() for query in queries: tzid = query_tzid(query, cal) print(query, "->", tzid) tzids.add(tzid) assert len(tzids) == len(queries) def test_we_do_not_miss_to_add_a_query(calendars): """Make sure all tzids are actually queried.""" cal = calendars.issue_722_missing_timezones raw = cal.raw_ics.decode() ids = set(findall("TZID=([a-zA-Z_/+-]+)", raw)) assert cal.get_used_tzids() == ids, "We find all tzids and they are unique." assert len(ids) == len(queries), "We query all the tzids." def test_unknown_tzid(calendars): """If we have an unknown tzid with no timezone component.""" cal = calendars.issue_722_missing_VTIMEZONE_custom assert "CUSTOM_tzid" in cal.get_used_tzids() assert "CUSTOM_tzid" in cal.get_missing_tzids() def test_custom_timezone_is_found_and_used(calendars): """Check the custom timezone component is not missing.""" cal = calendars.america_new_york assert "custom_America/New_York" in cal.get_used_tzids() assert "custom_America/New_York" not in cal.get_missing_tzids() def test_not_missing_anything(): """Check that no timezone is missing.""" cal = Calendar() assert cal.get_missing_tzids() == set() def test_utc_is_not_missing(calendars): """UTC should not be found missing.""" cal = calendars.issue_722_missing_timezones assert "UTC" not in cal.get_missing_tzids() assert "UTC" not in cal.get_used_tzids() def test_dateutil_timezone_is_not_found_with_tzname(calendars, no_pytz): """dateutil is an example of a timezone that has no tzid. In this test we make sure that the timezone is said to be missing. """ cal: Calendar = calendars.america_new_york cal.subcomponents.remove(cal.timezones[0]) assert cal.get_missing_tzids() == {"custom_America/New_York"} assert "dateutil" in repr(cal.events[0].start.tzinfo.__class__) @pytest.mark.parametrize("tzname", ["America/New_York", "Arctic/Longyearbyen"]) # @pytest.mark.parametrize("component", ["STANDARD", "DAYLIGHT"]) def test_dateutil_timezone_is_matched_with_tzname(tzname): """dateutil is an example of a timezone that has no tzid. In this test we make sure that the timezone is matched by its tzname() in the timezone in the STANDARD and DAYLIGHT components. """ cal = Calendar() event = Event() event.start = datetime(2024, 11, 12, tzinfo=gettz(tzname)) print(dir(event.start.tzinfo)) cal.add_component(event) assert cal.get_missing_tzids() == {tzname} cal.add_missing_timezones() assert cal.get_missing_tzids() == set() def test_dateutil_timezone_is_also_added(calendars): """We find and add a dateutil timezone. This is important as we use those in the zoneinfo implementation. """ @pytest.mark.parametrize( "calendar", [ "example", "america_new_york", # custom "timezone_same_start", # known tzid "period_with_timezone", # known tzid ], ) def test_timezone_is_not_missing(calendars, calendar): """Check that these calendars have no timezone missing.""" cal: Calendar = calendars[calendar] timezones = cal.timezones[:] assert set() == cal.get_missing_tzids() cal.add_missing_timezones() assert set() == cal.get_missing_tzids() assert cal.timezones == timezones def test_add_missing_known_timezones(calendars): """Add all timezones specified.""" cal: Calendar = calendars.issue_722_missing_timezones assert len(cal.timezones) == 0 cal.add_missing_timezones() assert len(cal.timezones) == len(queries), "all timezones are known" assert len(cal.get_missing_tzids()) == 0 def test_cannot_add_unknown_timezone(calendars): """I cannot add a timezone that is unknown.""" cal: Calendar = calendars.issue_722_missing_VTIMEZONE_custom assert len(cal.timezones) == 0 assert cal.get_missing_tzids() == {"CUSTOM_tzid"} cal.add_missing_timezones() assert cal.timezones == [], "we cannot add this timezone" assert cal.get_missing_tzids() == {"CUSTOM_tzid"} def test_cannot_create_a_timezone_from_an_invalid_tzid(): with pytest.raises(ValueError): Timezone.from_tzid("invalid/tzid") def test_dates_before_and_after_are_considered(): """When we add the timezones, we should check the calendar to see if all dates really occur in the span we use. We should also consider a huge default range. """ pytest.skip("todo") @pytest.mark.parametrize("tzid", available_timezones() - {"Factory", "localtime"}) def test_we_can_identify_dateutil_timezones(tzid): """dateutil and others were badly supported. But if we know their shortcodes, we should be able to identify them. """ tz = gettz(tzid) assert tz is None or tzid in tzids_from_tzinfo(tz) icalendar-6.3.1/src/icalendar/tests/test_issue_798_property_parameters.py000066400000000000000000000111621501302773300266570ustar00rootroot00000000000000"""Test the property parameters.""" from datetime import datetime import pytest from icalendar import ( CUTYPE, FBTYPE, PARTSTAT, RANGE, RELTYPE, vCalAddress, vDatetime, vPeriod, vText, ) from icalendar.parser import Parameters from icalendar.timezone.tzp import TZP class Prop: params: Parameters def __init__(self, **parameters): """Create a new property.""" self.params = Parameters(parameters) def to_ical(self) -> str: """Parameters to bytes to string.""" return self.params.to_ical().decode("utf-8") from icalendar.param import ( ALTREP, CN, CUTYPE, DELEGATED_FROM, DELEGATED_TO, RELTYPE, ) @pytest.fixture() def p(): """Empty test property.""" return Prop() def test_set_altrep(p): p.ALTREP = "http://example.com" assert p.params == {"ALTREP": "http://example.com"} assert p.ALTREP == "http://example.com" def test_altrep_must_be_quoted(): """altrepparam = "ALTREP" "=" DQUOTE uri DQUOTE""" assert Prop(ALTREP="1234aA").to_ical() == 'ALTREP="1234aA"' def test_del_altrep(p): """Del when empty""" del p.ALTREP assert p.params == {} assert p.ALTREP is None def test_del_altrep_full(p): """Del when empty""" p.ALTREP = "http://example.com" del p.ALTREP assert p.params == {} assert p.ALTREP is None def test_get_cutype(p): """The default is individual.""" assert p.CUTYPE == "INDIVIDUAL" def test_set_lowercase(): p = Prop(CUTYPE="individual") assert p.CUTYPE == "INDIVIDUAL" p.CUTYPE = "unknown" assert p.CUTYPE == CUTYPE.UNKNOWN def test_set_delegation_to_string(p): p.DELEGATED_FROM = "mailto:foo" assert p.DELEGATED_FROM == ("mailto:foo",) def test_set_delegation_to_tuple(p): p.DELEGATED_TO = ("mailto:foo","mailto:bar") assert p.DELEGATED_TO == ("mailto:foo","mailto:bar") def test_delete_delegation_to(p): p.DELEGATED_TO = ("mailto:foo","mailto:bar") del p.DELEGATED_TO assert p.DELEGATED_TO == () @pytest.mark.parametrize( ("value", "expected"), [ ((), ""), (("mailto:foo",), 'DELEGATED-TO="mailto:foo"'), (("mailto:foo","mailto:bar"), 'DELEGATED-TO="mailto:foo","mailto:bar"'), (('mailto:"asd"',), "DELEGATED-TO=\"mailto:^'asd^'\""), ] ) def test_serialize_delegation_to(p, value, expected): p.DELEGATED_TO = value assert p.to_ical() == expected @pytest.mark.parametrize( ("index", "expected"), [ (0, FBTYPE.BUSY_UNAVAILABLE), (1, FBTYPE.BUSY), (2, FBTYPE.FREE), ] ) def test_get_fbtype(calendars, index, expected): fb = calendars.issue_798_freebusy.freebusy[index] p : vPeriod = fb["FREEBUSY"] if isinstance(p, list): p = p[0] assert expected == p.FBTYPE @pytest.fixture() def addr(): return vCalAddress("mailto:foo") def test_partstat_get(addr: vCalAddress): """test the default partstat""" assert addr.PARTSTAT == "NEEDS-ACTION" def test_set_the_partstat(addr: vCalAddress): addr.PARTSTAT = PARTSTAT.ACCEPTED assert addr.PARTSTAT == "ACCEPTED" def test_this_and_future(): assert vDatetime(datetime(2019, 12, 10)).RANGE is None def test_this_and_future_set(): d = vDatetime(datetime(2019, 12, 10)) d.RANGE = RANGE.THISANDFUTURE assert d.params["RANGE"] == "THISANDFUTURE" def test_rsvp_default(addr): assert not addr.RSVP @pytest.mark.parametrize("rsvp", [True, False]) def test_set_rsvp(addr: vCalAddress, rsvp): addr.RSVP = rsvp assert addr.RSVP == rsvp assert addr.params["RSVP"] == ("TRUE" if rsvp else "FALSE") def test_sent_by(addr: vCalAddress): assert addr.SENT_BY is None def test_set_sent_by(addr: vCalAddress): addr.SENT_BY = "mailto:asd" assert addr.SENT_BY == "mailto:asd" assert addr.params["SENT-BY"] == "mailto:asd" assert addr.params.to_ical() == b'SENT-BY="mailto:asd"' @pytest.mark.parametrize("tzid", [None, "Europe/Berlin"]) def test_tzid(tzid, tzp:TZP): dt = vDatetime(tzp.localize(datetime(2019, 12, 10), tzid)) assert dt.TZID is None @pytest.mark.parametrize( ("index", "reltype"), [ (0, RELTYPE.PARENT), (1, RELTYPE.SIBLING), ] ) def test_reltype_example(calendars, index, reltype): """The reltype parameter in the examples.""" event = calendars.issue_798_related_to.events[index] print(event.to_ical().decode()) r : vText = event["RELATED-TO"] print(r) print(r.params) assert reltype == r.RELTYPE def test_set_reltype(p): p.RELTYPE = RELTYPE.CHILD assert p.RELTYPE == RELTYPE.CHILD assert p.params["RELTYPE"] == "CHILD" icalendar-6.3.1/src/icalendar/tests/test_issue_802.py000066400000000000000000000026211501302773300224520ustar00rootroot00000000000000"""Test the sequence and other int properies. https://www.rfc-editor.org/rfc/rfc5545#section-3.8.7.4 https://github.com/collective/icalendar/issues/802 """ import pytest from icalendar import Component, Event, Journal, Todo @pytest.fixture(params=[0, None]) def default_sequence(request): return request.param @pytest.fixture(params=[Event, Journal, Todo]) def component(request, default_sequence) -> Component: """Return a component.""" component : Component = request.param() if default_sequence is not None: component["SEQUENCE"] = default_sequence return component def test_sequence_is_0(component: Component): """Check the default value.""" assert component.sequence == 0 def test_increase_sequence(component: Component): """Check the default value.""" component.sequence += 1 assert component.sequence == 1 assert component["SEQUENCE"] == 1 def test_set_sequence(component: Component): """Check the default value.""" component.sequence = 400 assert component.sequence == 400 assert component["SEQUENCE"] == 400 def test_delete_sequence_default(component: Component): """Delete the value.""" del component.sequence assert component.sequence == 0 def test_delete_sequence_with_value(component: Component): """Delete the value.""" component.sequence = 400 del component.sequence assert component.sequence == 0 icalendar-6.3.1/src/icalendar/tests/test_issue_828.py000066400000000000000000000103751501302773300224670ustar00rootroot00000000000000"""Events differ although their times are equal.""" import contextlib from datetime import date, datetime, timedelta, timezone import pytest from icalendar import Event, Journal, vDate, vDatetime, vDDDLists, vDDDTypes try: from zoneinfo import ZoneInfo except ImportError: from backports.zoneinfo import ZoneInfo # type: ignore PGH003 def to_dt(a: date) -> date: return a def to_event(a): e = Event() e.start = a e.end = a + timedelta(days=1) e.add("RECURRENCE-ID", a - timedelta(days=1)) return e def to_journal(a): """return a journal for testing""" j = Journal() j.start = a j.end = a + timedelta(days=1) j.add("RECURRENCE-ID", a - timedelta(days=1)) return j def to_vDD(a): """Return a value type.""" if isinstance(a, datetime): return vDatetime(a) return vDate(a) def to_vDDDTypes(a): return vDDDTypes(a) def to_vDDDLists(a): return vDDDLists([a]) equal_dt_pairs = [ (date(1998, 10, 1), date(1998, 10, 1)), (datetime(2023, 12, 31), datetime(2023, 12, 31)), ( datetime(2023, 12, 31, tzinfo=timezone.utc), datetime(2023, 12, 31, tzinfo=ZoneInfo("UTC")), ), ( datetime(2023, 12, 31, tzinfo=ZoneInfo("UTC")), datetime(2023, 12, 31, tzinfo=ZoneInfo("UTC")), ), ( datetime(2025, 12, 31, tzinfo=ZoneInfo("UTC")), datetime(2025, 12, 31, tzinfo=ZoneInfo("GMT+0")), ), ( datetime(2025, 12, 31, 12, tzinfo=ZoneInfo("Europe/Zurich")), datetime(2025, 12, 31, 11, tzinfo=ZoneInfo("UTC")), ), ] param_equal_dts = pytest.mark.parametrize(("d1", "d2"), equal_dt_pairs) param_transform = pytest.mark.parametrize( "transform", [to_dt, to_event, to_journal, to_vDD, to_vDDDTypes, to_vDDDLists] ) @param_equal_dts @param_transform def test_equality_of_equal_datetimes(d1, d2, transform): """Check that the values are equal.""" a = transform(d1) b = transform(d2) assert_equal(a, b) def assert_equal(a, b): """Check all equality tests.""" assert a == b, f"1 equal: {a} == {b}" assert b == a, f"2 equal reversed: {b} == {a}" assert not a != b, f"3 not equal: {a} != {b}" # noqa: SIM202 assert not b != a, f"4 not equal reversed: {b} != {a}" # noqa: SIM202 assert bool(a) == bool(b), f"5 equal bool: {bool(a)} == {bool(b)}" with contextlib.suppress(TypeError): assert hash(a) == hash(b) def assert_not_eq(a, b): """Check all equality tests.""" assert a != b, f"1 unequal: {a} == {b}" assert b != a, f"2 unequal reversed: {b} == {a}" assert not a == b, f"3 not unequal: {a} != {b}" # noqa: SIM201 assert not b == a, f"4 not unequal reversed: {b} != {a}" # noqa: SIM201 dts = [to_dt, to_vDD, to_vDDDTypes, to_vDDDLists] @pytest.mark.parametrize("transform1", dts) @pytest.mark.parametrize("transform2", dts) @param_equal_dts def test_property_times_for_date_and_datetime(d1, d2, transform1, transform2): """Check the equality of the date and date implementations.""" a = transform1(d1) b = transform2(d2) assert_equal(a, b) def test_datetime_is_in_list_representation(): dt = datetime(2023, 12, 31) assert str(dt) in str(vDDDLists([dt])) unequal_pairs = [(a, b) for a, b in equal_dt_pairs if a != b] @pytest.mark.parametrize(("d1", "d2"), unequal_pairs) @param_transform def test_inequality(d1, d2, transform): """The values are not equal.""" a = transform(d1) b = transform(d2) assert_not_eq(a, b) @pytest.mark.parametrize("ddd_type", [to_vDD, to_vDDDTypes]) @param_equal_dts def test_not_equal_if_parameters_differ(d1, d2, ddd_type): """If the items are equal but the parameters differ, they should not be equal.""" d1 = ddd_type(d1) d2 = ddd_type(d2) d1.params["foo"] = "bar" assert_not_eq(d1, d2) @pytest.mark.parametrize("ddd_type", [to_vDD, to_vDDDTypes]) @param_equal_dts def test_equal_ignoring_x_params(d1, d2, ddd_type): """RFC 5545: Applications MUST ignore x-param and iana-param values they don't recognize.""" d1 = ddd_type(d1) d2 = ddd_type(d2) d1.params["x-foo"] = "bar" assert_equal(d1, d2) def test_list_is_not_hashable(): """We cannot hash a list because it changes.""" with pytest.raises(TypeError): hash(vDDDLists([datetime(2023, 12, 31)])) icalendar-6.3.1/src/icalendar/tests/test_issue_836_do_not_quote_tzid.py000066400000000000000000000045151501302773300262760ustar00rootroot00000000000000"""Quoting the TZID parameter creates compatibility problems. See https://github.com/collective/icalendar/issues/836 :rfc:`5545`: .. code-block:: text param-value = paramtext / quoted-string paramtext = *SAFE-CHAR quoted-string = DQUOTE *QSAFE-CHAR DQUOTE SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E / NON-US-ASCII ; Any character except CONTROL, DQUOTE, ";", ":", "," NON-US-ASCII = UTF8-2 / UTF8-3 / UTF8-4 ; UTF8-2, UTF8-3, and UTF8-4 are defined in [RFC3629] """ from datetime import datetime, time from icalendar import Event, Parameters, vDDDTypes, vDatetime, vPeriod, vTime import pytest from icalendar.prop import vDDDLists # All the controls except HTAB CONTROL = { i for i in range(256) if 0x00 <= i <= 0x08 or 0x0A <= i <= 0x1F or i == 0x7F } # Any character except CONTROL, DQUOTE, ";", ":", "," SAFE_CHAR = set(range(256)) - CONTROL - set(b'";:,') param_tzid = pytest.mark.parametrize( "tzid", [ "Europe/London", "Eastern Standard Time", ] ) @param_tzid @pytest.mark.parametrize( "vdt", [ vDatetime(datetime(2024, 10, 11, 12, 0)), vTime(time(23, 59, 59)), vDDDTypes(datetime(2024, 10, 11, 12, 0)), vPeriod((datetime(2024, 10, 11, 12, 0), datetime(2024, 10, 11, 13, 0))), vDDDLists([datetime(2024, 10, 11, 12, 0)]), ] ) def test_parameter_is_not_quoted_when_not_needed(tzid, vdt): """Check that serializing the value works without quoting.""" e = Event() vdt.params["TZID"] = tzid e["DTSTART"] = vdt ics = e.to_ical().decode() print(ics) assert tzid in ics assert f'"{tzid}' not in ics assert f'{tzid}"' not in ics @pytest.mark.parametrize( "safe_char", list(map(chr, sorted(SAFE_CHAR))) ) def test_safe_char_is_not_escaped(safe_char): """Check that paramerter serialization is without quotes for safe chars.""" params = Parameters(tzid=safe_char) result = params.to_ical().decode() assert '"' not in result def test_get_calendar_and_serialize_it_wihtout_quotes(calendars): """The example calendar should not contain the timezone with quotes.""" ics = calendars.issue_836_do_not_quote_tzid.to_ical().decode() assert '"Eastern Standard' not in ics assert 'Standard Time"' not in ics assert "Eastern Standard Time" in ics icalendar-6.3.1/src/icalendar/tests/test_multiple.py000066400000000000000000000017511501302773300225670ustar00rootroot00000000000000"""Testing multiple VCALENDAR components and multiple VEVENT components""" from icalendar.prop import vText from icalendar.cal import Event import pytest def test_multiple(calendars): """Check opening multiple calendars.""" cals = calendars.multiple.multiple_calendar_components assert len(cals) == 2 assert [comp.name for comp in cals[0].walk()] == ["VCALENDAR", "VEVENT"] assert [comp.name for comp in cals[1].walk()] == ["VCALENDAR", "VEVENT", "VEVENT"] assert cals[0]["prodid"] == vText( "-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN" ) def test_multiple_events(): """Raises ValueError unless multiple=True""" event_components=""" BEGIN:VEVENT END:VEVENT BEGIN:VEVENT END:VEVENT """ with pytest.raises(ValueError) as exception: Event.from_ical(event_components, multiple=False) def test_missing_event(): """Raises ValueError if no component found""" with pytest.raises(ValueError) as exception: Event.from_ical('') icalendar-6.3.1/src/icalendar/tests/test_oss_fuzz_errors.py000066400000000000000000000011071501302773300242050ustar00rootroot00000000000000"""This file collects errors that the OSS FUZZ build has found.""" from datetime import time import pytest from icalendar import Calendar from icalendar.prop import vDDDLists def test_stack_is_empty(): """If we get passed an invalid string, we expect to get a ValueError.""" with pytest.raises(ValueError): Calendar.from_ical("END:CALENDAR") def test_vdd_list_type_mismatch(): """If we pass in a string type, we expect it to be converted to bytes""" vddd_list = vDDDLists([time(hour=6, minute=6, second=6)]) assert vddd_list.to_ical() == b"060606" icalendar-6.3.1/src/icalendar/tests/test_parsing.py000066400000000000000000000217061501302773300224010ustar00rootroot00000000000000"""Tests checking that parsing works""" import base64 from datetime import datetime import pytest from icalendar import Calendar, Event, vBinary, vRecur from icalendar.parser import Contentline, Parameters, unescape_char @pytest.mark.parametrize( "calendar_name", [ # Issue #178 - A component with an unknown/invalid name is represented # as one of the known components, the information about the original # component name is lost. # https://github.com/collective/icalendar/issues/178 https://github.com/collective/icalendar/pull/180 # Parsing of a nonstandard component "issue_178_component_with_invalid_name_represented", # Nonstandard component inside other components, also has properties "issue_178_custom_component_inside_other", # Nonstandard component is able to contain other components "issue_178_custom_component_contains_other", ], ) def test_calendar_to_ical_is_inverse_of_from_ical(calendars, calendar_name): calendar = getattr(calendars, calendar_name) assert calendar.to_ical().splitlines() == calendar.raw_ics.splitlines() assert calendar.to_ical() == calendar.raw_ics @pytest.mark.parametrize( ("raw_content_line", "expected_output"), [ # Issue #142 - Multivalued parameters. This is needed for VCard 3.0. # see https://github.com/collective/icalendar/pull/142 ( "TEL;TYPE=HOME,VOICE:000000000", ("TEL", Parameters({"TYPE": ["HOME", "VOICE"]}), "000000000"), ), # Issue #143 - Allow dots in property names. Another vCard related issue. # see https://github.com/collective/icalendar/pull/143 ( "ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR:;;This is the Adress 08; Some City;;12345;Germany", ( "ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR", Parameters(), ";;This is the Adress 08; Some City;;12345;Germany", ), ), ( "ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL:", ( "ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL", Parameters(), "", ), ), ], ) def test_content_lines_parsed_properly(raw_content_line, expected_output): assert Contentline.from_ical(raw_content_line).parts() == expected_output @pytest.mark.parametrize( "timezone_info", [ # General timezone aware dates in ical string (b"DTSTART;TZID=America/New_York:20130907T120000"), (b"DTEND;TZID=America/New_York:20130907T170000"), # Specific timezone aware exdates in ical string (b"EXDATE;TZID=America/New_York:20131012T120000"), (b"EXDATE;TZID=America/New_York:20131011T120000"), ], ) def test_timezone_info_present_in_ical_issue_112(events, timezone_info): """Issue #112 - No timezone info on EXDATE https://github.com/collective/icalendar/issues/112 """ assert timezone_info in events.issue_112_missing_tzinfo_on_exdate.to_ical() def test_timezone_name_parsed_issue_112(events): """Issue #112 - No timezone info on EXDATE https://github.com/collective/icalendar/issues/112 """ assert ( events.issue_112_missing_tzinfo_on_exdate["exdate"][0].dts[0].dt.tzname() == "EDT" ) def test_issue_157_removes_trailing_semicolon(events): """Issue #157 - Recurring rules and trailing semicolons https://github.com/collective/icalendar/pull/157 """ recur = events.issue_157_removes_trailing_semicolon.decoded("RRULE") assert isinstance(recur, vRecur) assert recur.to_ical() == b"FREQ=YEARLY;BYDAY=1SU;BYMONTH=11" @pytest.mark.parametrize( "event_name", [ # https://github.com/collective/icalendar/pull/100 ("issue_100_transformed_doctests_into_unittests"), ("issue_184_broken_representation_of_period"), # PERIOD should be put back into shape "issue_156_RDATE_with_PERIOD", "issue_156_RDATE_with_PERIOD_list", "event_with_unicode_organizer", ], ) def test_event_to_ical_is_inverse_of_from_ical(events, event_name): """Make sure that an event's ICS is equal to the ICS it was made from.""" event = events[event_name] assert event.to_ical().splitlines() == event.raw_ics.splitlines() assert event.to_ical() == event.raw_ics def test_decode_rrule_attribute_error_issue_70(events): # Issue #70 - e.decode("RRULE") causes Attribute Error # see https://github.com/collective/icalendar/issues/70 recur = events.issue_70_rrule_causes_attribute_error.decoded("RRULE") assert isinstance(recur, vRecur) assert recur.to_ical() == b"FREQ=WEEKLY;UNTIL=20070619T225959;INTERVAL=1" def test_description_parsed_properly_issue_53(events): """Issue #53 - Parsing failure on some descriptions? https://github.com/collective/icalendar/issues/53 """ assert ( b"July 12 at 6:30 PM" in events.issue_53_description_parsed_properly["DESCRIPTION"].to_ical() ) def test_raises_value_error_for_properties_without_parent_pull_179(): """Found an issue where from_ical() would raise IndexError for properties without parent components. https://github.com/collective/icalendar/pull/179 """ with pytest.raises(ValueError): Calendar.from_ical("VERSION:2.0") def test_tzid_parsed_properly_issue_53(timezones): """Issue #53 - Parsing failure on some descriptions? https://github.com/collective/icalendar/issues/53 """ assert ( timezones.issue_53_tzid_parsed_properly["tzid"].to_ical() == b"America/New_York" ) def test_timezones_to_ical_is_inverse_of_from_ical(timezones): """Issue #55 - Parse error on utc-offset with seconds value see https://github.com/collective/icalendar/issues/55""" timezone = timezones["issue_55_parse_error_on_utc_offset_with_seconds"] assert timezone.to_ical() == timezone.raw_ics @pytest.mark.parametrize( ("date", "expected_output"), [ (datetime(2012, 7, 16, 0, 0, 0), b"DTSTART:20120716T000000Z"), (datetime(2021, 11, 17, 15, 9, 15), b"DTSTART:20211117T150915Z"), ], ) def test_no_tzid_when_utc(utc, date, expected_output): """Issue #58 - TZID on UTC DATE-TIMEs Issue #335 - UTC timezone identification is broken https://github.com/collective/icalendar/issues/58 https://github.com/collective/icalendar/issues/335 """ # According to RFC 5545: "The TZID property parameter MUST NOT be # applied to DATE-TIME or TIME properties whose time values are # specified in UTC. date = date.replace(tzinfo=utc) event = Event() event.add("dtstart", date) assert expected_output in event.to_ical() def test_vBinary_base64_encoded_issue_82(): """Issue #82 - vBinary __repr__ called rather than to_ical from container types https://github.com/collective/icalendar/issues/82 """ b = vBinary("text") b.params["FMTTYPE"] = "text/plain" assert b.to_ical() == base64.b64encode(b"text") def test_creates_event_with_base64_encoded_attachment_issue_82(events): """Issue #82 - vBinary __repr__ called rather than to_ical from container types https://github.com/collective/icalendar/issues/82 """ b = vBinary("text") b.params["FMTTYPE"] = "text/plain" event = Event() event.add("ATTACH", b) assert event.to_ical() == events.issue_82_expected_output.raw_ics @pytest.mark.parametrize( "calendar_name", [ # Issue #466 - [BUG] TZID timezone is ignored when forward-slash is used # https://github.com/collective/icalendar/issues/466 "issue_466_respect_unique_timezone", "issue_466_convert_tzid_with_slash", ], ) def test_handles_unique_tzid(calendars, in_timezone, calendar_name): calendar = calendars[calendar_name] event = calendar.walk("VEVENT")[0] print(vars(event)) start_dt = event["dtstart"].dt end_dt = event["dtend"].dt assert start_dt == in_timezone(datetime(2022, 10, 21, 20, 0, 0), "Europe/Stockholm") assert end_dt == in_timezone(datetime(2022, 10, 21, 21, 0, 0), "Europe/Stockholm") @pytest.mark.parametrize( ("event_name", "expected_cn", "expected_ics"), [ ( "event_with_escaped_characters", r"that, that; %th%%at%\ that:", "это, то; that\\ %th%%at%:", ), ("event_with_escaped_character1", r"Society, 2014", "that"), ("event_with_escaped_character2", r"Society\ 2014", "that"), ("event_with_escaped_character3", r"Society; 2014", "that"), ("event_with_escaped_character4", r"Society: 2014", "that"), ], ) def test_escaped_characters_read(event_name, expected_cn, expected_ics, events): event = events[event_name] assert event["ORGANIZER"].params["CN"] == expected_cn assert event["ORGANIZER"].to_ical() == expected_ics.encode("utf-8") def test_unescape_char(): assert unescape_char(b"123") == b"123" assert unescape_char(b"\\n") == b"\n" icalendar-6.3.1/src/icalendar/tests/test_period.py000066400000000000000000000076421501302773300222230ustar00rootroot00000000000000"""These tests make sure that we have some coverage on the usage of the PERIOD value type. See - https://github.com/collective/icalendar/issues/156 - https://github.com/pimutils/khal/issues/152#issuecomment-933635248 """ import datetime import pytest from icalendar.prop import vDDDTypes, vPeriod @pytest.mark.parametrize( ("calname", "tzname", "index", "period_string"), [ ( "issue_156_RDATE_with_PERIOD_TZID_khal_2", "Europe/Berlin", 0, "20211101T160000/20211101T163000", ), ( "issue_156_RDATE_with_PERIOD_TZID_khal_2", "Europe/Berlin", 1, "20211206T160000/20211206T163000", ), ( "issue_156_RDATE_with_PERIOD_TZID_khal_2", "Europe/Berlin", 2, "20220103T160000/20220103T163000", ), ( "issue_156_RDATE_with_PERIOD_TZID_khal_2", "Europe/Berlin", 3, "20220207T160000/20220207T163000", ), ] + [ ("issue_156_RDATE_with_PERIOD_TZID_khal", "America/Chicago", i, period) for i, period in enumerate( ( "20180327T080000/20180327T0" "90000,20180403T080000/20180403T090000,20180410T080000/20180410T090000,2018" "0417T080000/20180417T090000,20180424T080000/20180424T090000,20180501T08000" "0/20180501T090000,20180508T080000/20180508T090000,20180515T080000/20180515" "T090000,20180522T080000/20180522T090000,20180529T080000/20180529T090000,20" "180605T080000/20180605T090000,20180612T080000/20180612T090000,20180619T080" "000/20180619T090000,20180626T080000/20180626T090000,20180703T080000/201807" "03T090000,20180710T080000/20180710T090000,20180717T080000/20180717T090000," "20180724T080000/20180724T090000,20180731T080000/20180731T090000" ).split(",") ) ], ) def test_issue_156_period_list_in_rdate( calendars, calname, tzname, index, period_string ): """Check items in a list of period values.""" calendar = calendars[calname] rdate = calendar.walk("vevent")[0]["rdate"] period = rdate.dts[index] assert period.dt == vDDDTypes.from_ical(period_string, timezone=tzname) def test_duration_properly_parsed(events): """This checks the duration PT5H30M.""" start = vDDDTypes.from_ical("19970109T180000Z") duration = vDDDTypes.from_ical("PT5H30M") rdate = events.issue_156_RDATE_with_PERIOD_list["RDATE"] print(rdate) period = rdate.dts[1].dt print(dir(duration)) assert period[0] == start assert period[1].days == 0 assert period[1].seconds == (5 * 60 + 30) * 60 assert period[1] == duration def test_tzid_is_part_of_the_parameters(calendars): """The TZID should be mentioned in the parameters.""" event = list(calendars.period_with_timezone.walk("VEVENT"))[0] assert event["RDATE"].params["TZID"] == "America/Vancouver" def test_tzid_is_part_of_the_period_values(calendars, tzp): """The TZID should be set in the datetime.""" event = list(calendars.period_with_timezone.walk("VEVENT"))[0] start, end = event["RDATE"].dts[0].dt assert start == tzp.localize( datetime.datetime(2023, 12, 13, 12), "America/Vancouver" ) assert end == tzp.localize(datetime.datetime(2023, 12, 13, 15), "America/Vancouver") def test_period_overlaps(): # 30 minute increments datetime_1 = datetime.datetime(2024, 11, 20, 12, 0) # 12:00 datetime_2 = datetime.datetime(2024, 11, 20, 12, 30) # 12:30 datetime_3 = datetime.datetime(2024, 11, 20, 13, 0) # 13:00 period_1 = vPeriod((datetime_1, datetime_2)) period_2 = vPeriod((datetime_1, datetime_3)) period_3 = vPeriod((datetime_2, datetime_3)) assert period_1.overlaps(period_2) assert period_3.overlaps(period_2) assert not period_1.overlaps(period_3) icalendar-6.3.1/src/icalendar/tests/test_property_params.py000066400000000000000000000120761501302773300241650ustar00rootroot00000000000000import re import pytest from icalendar import Calendar, Event, Parameters, vCalAddress @pytest.mark.parametrize( ("parameter", "expected"), [ # Simple parameter:value pair (Parameters(parameter1="Value1"), b"PARAMETER1=Value1"), # Parameter with list of values must be separated by comma (Parameters({"parameter1": ["Value1", "Value2"]}), b"PARAMETER1=Value1,Value2"), # Multiple parameters must be separated by a semicolon ( Parameters({"RSVP": "TRUE", "ROLE": "REQ-PARTICIPANT"}), b"ROLE=REQ-PARTICIPANT;RSVP=TRUE", ), # Parameter values containing ',;:' must be double quoted (Parameters({"ALTREP": "http://www.wiz.org"}), b'ALTREP="http://www.wiz.org"'), # list items must be quoted separately ( Parameters( {"MEMBER": ["MAILTO:projectA@host.com", "MAILTO:projectB@host.com"]} ), b'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"', ), ( Parameters( { "parameter1": "Value1", "parameter2": ["Value2", "Value3"], "ALTREP": ["http://www.wiz.org", "value4"], } ), b'ALTREP="http://www.wiz.org","value4";PARAMETER1=Value1;PARAMETER2=Value2,Value3', ), # Including empty strings (Parameters({"PARAM": ""}), b"PARAM="), # We can also parse parameter strings ( Parameters( {"MEMBER": ["MAILTO:projectA@host.com", "MAILTO:projectB@host.com"]} ), b'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"', ), # We can also parse parameter strings ( Parameters( { "PARAMETER1": "Value1", "ALTREP": ["http://www.wiz.org", "value4"], "PARAMETER2": ["Value2", "Value3"], } ), b'ALTREP="http://www.wiz.org","value4";PARAMETER1=Value1;PARAMETER2=Value2,Value3', ), ], ) def test_parameter_to_ical_is_inverse_of_from_ical(parameter, expected): assert parameter.to_ical() == expected assert Parameters.from_ical(expected.decode("utf-8")) == parameter def test_parse_parameter_string_without_quotes(): assert Parameters.from_ical("PARAM1=Value 1;PARA2=Value 2") == Parameters( {"PARAM1": "Value 1", "PARA2": "Value 2"} ) def test_parametr_is_case_insensitive(): parameter = Parameters(parameter1="Value1") assert parameter["parameter1"] == parameter["PARAMETER1"] == parameter["PaRaMeTer1"] def test_parameter_keys_are_uppercase(): parameter = Parameters(parameter1="Value1") assert list(parameter.keys()) == ["PARAMETER1"] @pytest.mark.parametrize( ("cn_param", "cn_quoted"), [ # not double-quoted ("Aramis", "Aramis"), # if a space is present - enclose in double quotes ("Aramis Alameda", '"Aramis Alameda"'), # a single quote in parameter value - double quote the value ("Aramis d'Alameda", '"Aramis d\'Alameda"'), ("Арамис д'Аламеда", '"Арамис д\'Аламеда"'), # Before, double quote is replaced with single quote # Since RFC 6868, we replace this with ^' ('Aramis d"Alameda', '"Aramis d^\'Alameda"'), ], ) def test_quoting(cn_param, cn_quoted): event = Event() attendee = vCalAddress("test@example.com") attendee.params["CN"] = cn_param event.add("ATTENDEE", attendee) assert f"ATTENDEE;CN={cn_quoted}:test@example.com" in event.to_ical().decode( "utf-8" ) def test_property_params(): """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""" ) assert ical_str == exp_str # other way around: ensure the property parameters can be restored from # an icalendar string. ical2 = Calendar.from_ical(ical_str) assert ical2.get("ORGANIZER").params.get("CN") == "Doe, John" def test_parse_and_access_property_params(calendars): """Parse an ics string and access some property parameters then. This is a follow-up of a question received per email. """ event = calendars.property_params.walk("VEVENT")[0] attendee = event["attendee"][0] assert attendee.to_ical() == b"MAILTO:rembrand@xs4all.nl" assert attendee.params.to_ical() == b"CN=RembrandXS;PARTSTAT=NEEDS-ACTION;RSVP=TRUE" assert attendee.params["cn"] == "RembrandXS" def test_repr(): """Test correct class representation.""" it = Parameters(parameter1="Value1") assert re.match(r"Parameters\({u?'PARAMETER1': u?'Value1'}\)", str(it)) icalendar-6.3.1/src/icalendar/tests/test_pytz_zoneinfo_integration.py000066400000000000000000000062751501302773300262620ustar00rootroot00000000000000"""This tests the switch to different timezone implementations. These are mostly located in icalendar.timezone. """ try: import pytz from icalendar.timezone.pytz import PYTZ except ImportError: pytz = None import copy import pickle from datetime import datetime import pytest from dateutil.rrule import MONTHLY, rrule from dateutil.tz.tz import _tzicalvtz from icalendar.timezone.zoneinfo import ZONEINFO, zoneinfo if pytz: PYTZ_TIMEZONES = pytz.all_timezones TZP_ = [PYTZ(), ZONEINFO()] NEW_TZP_NAME = ["pytz", "zoneinfo"] else: PYTZ_TIMEZONES = [] TZP_ = [ZONEINFO()] NEW_TZP_NAME = ["zoneinfo"] @pytest.mark.parametrize( "tz_name", PYTZ_TIMEZONES + list(zoneinfo.available_timezones()) ) @pytest.mark.parametrize("tzp_", TZP_) def test_timezone_names_are_known(tz_name, tzp_): """Make sure that all timezones are understood.""" if tz_name in ("Factory", "localtime"): pytest.skip() assert tzp_.knows_timezone_id( tz_name ), f"{tzp_.__class__.__name__} should know {tz_name}" @pytest.mark.parametrize("func", [pickle.dumps, copy.copy, copy.deepcopy]) @pytest.mark.parametrize( "obj", [ _tzicalvtz("id"), rrule(freq=MONTHLY, count=4, dtstart=datetime(2028, 10, 1), cache=True), ], ) def test_can_pickle_timezone(func, tzp, obj): """Check that we can serialize and copy timezones.""" func(obj) def test_copied_rrule_is_the_same(): """When we copy an rrule, we want it to be the same after this.""" r = rrule(freq=MONTHLY, count=4, dtstart=datetime(2028, 10, 1), cache=True) assert str(copy.deepcopy(r)) == str(r) def test_tzp_properly_switches(tzp, tzp_name): """We want the default implementation to switch.""" assert (tzp_name == "pytz") == tzp.uses_pytz() def test_tzp_is_pytz_only(tzp, tzp_name, pytz_only): """We want the default implementation to switch.""" assert tzp_name == "pytz" assert tzp.uses_pytz() def test_cache_reuse_timezone_cache(tzp, timezones): """Make sure we do not cache the timezones twice and change them.""" tzp.cache_timezone_component(timezones.pacific_fiji) tzp1 = tzp.timezone("custom_Pacific/Fiji") assert tzp1 is tzp.timezone("custom_Pacific/Fiji") tzp.cache_timezone_component(timezones.pacific_fiji) assert tzp1 is tzp.timezone("custom_Pacific/Fiji"), "Cache is not replaced." @pytest.mark.parametrize("new_tzp_name", NEW_TZP_NAME) def test_cache_is_emptied_when_tzp_is_switched(tzp, timezones, new_tzp_name): """Make sure we do not reuse the timezones created when we switch the provider.""" tzp.cache_timezone_component(timezones.pacific_fiji) tz1 = tzp.timezone("custom_Pacific/Fiji") tzp.use(new_tzp_name) tzp.cache_timezone_component(timezones.pacific_fiji) tz2 = tzp.timezone("custom_Pacific/Fiji") assert tz1 is not tz2 def test_invalid_name(tzp): """Check that the provider name is OK.""" provider = "invalid_provider" with pytest.raises(ValueError) as e: tzp.use(provider) # f"Unknown provider {provider}. Use 'pytz' or 'zoneinfo'." message = e.value.args[0] assert f"Unknown provider {provider}." in message assert "zoneinfo" in message assert "pytz" in message icalendar-6.3.1/src/icalendar/tests/test_recurrence.py000066400000000000000000000117431501302773300230730ustar00rootroot00000000000000from datetime import date, datetime import pytest from icalendar import Event def test_recurrence_properly_parsed(events): assert events.event_with_recurrence["rrule"] == {"COUNT": [100], "FREQ": ["DAILY"]} @pytest.mark.parametrize( ("i", "exception_date"), [ (0, datetime(1996, 4, 2, 1, 0)), (1, datetime(1996, 4, 3, 1, 0)), (2, datetime(1996, 4, 4, 1, 0)), ], ) def test_exdate_properly_parsed(events, i, exception_date, in_timezone): assert events.event_with_recurrence["exdate"].dts[i].dt == in_timezone( exception_date, "UTC" ) def test_exdate_properly_marshalled(events): actual = events.event_with_recurrence["exdate"].to_ical() assert actual == b"19960402T010000Z,19960403T010000Z,19960404T010000Z" # 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: def test_exdate_formed_from_exdates_on_multiple_lines_is_a_list(events): exdate = events.event_with_recurrence_exdates_on_different_lines["exdate"] assert isinstance(exdate, list) @pytest.mark.parametrize( ("i", "exception_date", "exception_date_ics"), [ (0, datetime(2012, 5, 29, 10, 0), b"20120529T100000"), (1, datetime(2012, 4, 3, 10, 0), b"20120403T100000"), (2, datetime(2012, 4, 10, 10, 0), b"20120410T100000"), (3, datetime(2012, 5, 1, 10, 0), b"20120501T100000"), (4, datetime(2012, 4, 17, 10, 0), b"20120417T100000"), ], ) def test_list_exdate_to_ical_is_inverse_of_from_ical( events, i, exception_date, exception_date_ics, in_timezone ): exdate = events.event_with_recurrence_exdates_on_different_lines["exdate"] assert exdate[i].dts[0].dt == in_timezone(exception_date, "Europe/Vienna") assert exdate[i].to_ical() == exception_date_ics @pytest.mark.parametrize( ("freq", "byday", "dtstart", "expected"), [ # Test some YEARLY BYDAY repeats ( "YEARLY", "1SU", date(2016, 1, 3), # 1st Sunday in year b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 1SU\r\nDTSTART;VALUE=DATE:20160103\r\nRRULE:FREQ=YEARLY;BYDAY=1SU\r\nEND:VEVENT\r\n", ), ( "YEARLY", "53MO", date(1984, 12, 31), # 53rd Monday in (leap) year b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 53MO\r\nDTSTART;VALUE=DATE:19841231\r\nRRULE:FREQ=YEARLY;BYDAY=53MO\r\nEND:VEVENT\r\n", ), ( "YEARLY", "-1TU", date(1999, 12, 28), # Last Tuesday in year b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY -1TU\r\nDTSTART;VALUE=DATE:19991228\r\nRRULE:FREQ=YEARLY;BYDAY=-1TU\r\nEND:VEVENT\r\n", ), ( "YEARLY", "-17WE", date(2000, 9, 6), # 17th-to-last Wednesday in year b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY -17WE\r\nDTSTART;VALUE=DATE:20000906\r\nRRULE:FREQ=YEARLY;BYDAY=-17WE\r\nEND:VEVENT\r\n", ), # Test some MONTHLY BYDAY repeats ( "MONTHLY", "2TH", date(2003, 4, 10), # 2nd Thursday in month b"BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY 2TH\r\nDTSTART;VALUE=DATE:20030410\r\nRRULE:FREQ=MONTHLY;BYDAY=2TH\r\nEND:VEVENT\r\n", ), ( "MONTHLY", "-3FR", date(2017, 5, 12), # 3rd-to-last Friday in month b"BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY -3FR\r\nDTSTART;VALUE=DATE:20170512\r\nRRULE:FREQ=MONTHLY;BYDAY=-3FR\r\nEND:VEVENT\r\n", ), ( "MONTHLY", "-5SA", date(2053, 11, 1), # 5th-to-last Saturday in month b"BEGIN:VEVENT\r\nSUMMARY:Event MONTHLY -5SA\r\nDTSTART;VALUE=DATE:20531101\r\nRRULE:FREQ=MONTHLY;BYDAY=-5SA\r\nEND:VEVENT\r\n", ), # Specifically test examples from the report of Issue #518 # https://github.com/collective/icalendar/issues/518 ( "YEARLY", "9MO", date(2023, 2, 27), # 9th Monday in year b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 9MO\r\nDTSTART;VALUE=DATE:20230227\r\nRRULE:FREQ=YEARLY;BYDAY=9MO\r\nEND:VEVENT\r\n", ), ( "YEARLY", "10MO", date(2023, 3, 6), # 10th Monday in year b"BEGIN:VEVENT\r\nSUMMARY:Event YEARLY 10MO\r\nDTSTART;VALUE=DATE:20230306\r\nRRULE:FREQ=YEARLY;BYDAY=10MO\r\nEND:VEVENT\r\n", ), ], ) def test_byday_to_ical(freq, byday, dtstart, expected): """Test the BYDAY rule is correctly processed by to_ical().""" event = Event() event.add("SUMMARY", " ".join(["Event", freq, byday])) event.add("DTSTART", dtstart) event.add("RRULE", {"FREQ": [freq], "BYDAY": byday}) assert event.to_ical() == expected icalendar-6.3.1/src/icalendar/tests/test_rfc_6868.py000066400000000000000000000060461501302773300222030ustar00rootroot00000000000000"""This implemensts RFC 6868. There are only some changes to parameters needed. """ import os from typing import TYPE_CHECKING import pytest from icalendar import Calendar from icalendar.parser import dquote, rfc_6868_escape, rfc_6868_unescape if TYPE_CHECKING: from icalendar import vCalAddress, vText param_duplicate = pytest.mark.parametrize( "duplicate", [ lambda x:x, lambda cal: Calendar.from_ical(cal.to_ical()) ] ) @param_duplicate def test_rfc_6868_example(calendars, duplicate): """Check the example from the RFC.""" cal : Calendar = duplicate(calendars.rfc_6868) attendee : vCalAddress = cal.events[0]["attendee"] assert attendee.name == 'George Herman "Babe" Ruth' @param_duplicate def test_all_parameters(calendars, duplicate): """Check that all examples get decoded correctly.""" cal : Calendar = duplicate(calendars.rfc_6868) param = cal["X-PARAM"].params["ALL"] assert param == '^"\n' @param_duplicate def test_unknown_character(calendars, duplicate): """if a ^ (U+005E) character is followed by any character other than the ones above, parsers MUST leave both the ^ and the following character in place""" cal : Calendar = duplicate(calendars.rfc_6868) param = cal["X-PARAM"].params["UNKNOWN"] assert param == "^a^ ^asd" @pytest.mark.parametrize( ("text", "expected"), [ ("^a", "^a"), ("^^", "^"), # ("^n", ), # see other test ("^'", '"'), ("^^a^b", "^a^b") ], ) @pytest.mark.parametrize( "modify", [lambda x: x, lambda x: x*10, lambda x: f"asd{x}aaA^AA"] ) def test_unescape(text, expected, modify): """Check unescaping.""" result = rfc_6868_unescape(modify(text)) assert result == modify(expected) @pytest.mark.parametrize( "newline", ["\n", "\r\n", "\r", os.linesep]) def test_unescape_newline(newline, monkeypatch): """Unescape the newline.""" monkeypatch.setattr(os, "linesep", newline) assert rfc_6868_unescape("^n") == newline param_values = pytest.mark.parametrize("text, expected", [ ("", ""), ("^", "^^"), ("^n", "^^n"), ("This text\n has\r several\r\n lines", "This text^n has^n several^n lines"), ('Call me "Harry".', "Call me ^'Harry^'."), ] ) @param_values def test_escape_rfc_6868(text, expected): """Check that we can escape the content with special characters.""" escaped = rfc_6868_escape(text) assert escaped == expected, f"{escaped!r} == {expected!r}" assert rfc_6868_escape(rfc_6868_unescape(escaped)) == expected @param_values def test_escaping_parameters(text, expected): cal = Calendar() cal.add("X-Param", "asd") param : vText = cal["X-PARAM"] param.params["RFC6868"] = text ical = cal.to_ical().decode() print(ical) assert "X-PARAM;RFC6868=" + dquote(expected) in ical def test_encode_example_again(calendars): """The example file should yield its content again.""" cal : Calendar = calendars.rfc_6868 again = Calendar.from_ical(cal.to_ical()) assert cal == again assert cal.to_ical() == again.to_ical() icalendar-6.3.1/src/icalendar/tests/test_rfc_7529.py000066400000000000000000000047131501302773300221750ustar00rootroot00000000000000"""This tests the compatibility with RFC 7529. See - https://github.com/collective/icalendar/issues/655 - https://www.rfc-editor.org/rfc/rfc7529.html """ import pytest from icalendar.prop import vMonth, vRecur, vSkip @pytest.mark.parametrize( ("uid", "scale"), [ ("4.3.1", "CHINESE"), ("4.3.2", "ETHIOPIC"), ("4.3.3", "HEBREW"), ("4.3.4", "GREGORIAN"), ], ) def test_rscale(calendars, uid, scale): """Check that the RSCALE is parsed correctly.""" event = calendars.rfc_7529.walk(select=lambda c: c.get("UID") == uid)[0] print(event.errors) rrule = event["RRULE"] print(rrule) assert rrule["RSCALE"] == [scale] @pytest.mark.parametrize( ("uid", "skip"), [ ("4.3.2", None), ("4.3.3", ["FORWARD"]), ], ) def test_rscale_with_skip(calendars, uid, skip): """Check that the RSCALE is parsed correctly.""" event = calendars.rfc_7529.walk(select=lambda c: c.get("UID") == uid)[0] recur = event["RRULE"] assert recur.get("SKIP") == skip def test_leap_month(calendars): """Check that we can parse the leap month.""" event = calendars.rfc_7529.walk(select=lambda c: c.get("UID") == "4.3.3")[0] recur = event["RRULE"] assert recur["BYMONTH"][0].leap is True @pytest.mark.parametrize( ("ty", "recur", "ics"), [ ( vRecur, vRecur(rscale="CHINESE", freq="YEARLY"), b"RSCALE=CHINESE;FREQ=YEARLY", ), (vRecur, vRecur(bymonth=vMonth(10)), b"BYMONTH=10"), (vRecur, vRecur(bymonth=vMonth("5L")), b"BYMONTH=5L"), (vMonth, vMonth(10), b"10"), (vMonth, vMonth("5L"), b"5L"), (vSkip, vSkip.OMIT, b"OMIT"), (vSkip, vSkip.BACKWARD, b"BACKWARD"), (vSkip, vSkip.FORWARD, b"FORWARD"), (vSkip, vSkip("OMIT"), b"OMIT"), (vSkip, vSkip("BACKWARD"), b"BACKWARD"), (vSkip, vSkip("FORWARD"), b"FORWARD"), ( vRecur, vRecur(rscale="GREGORIAN", freq="YEARLY", skip="FORWARD"), b"RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD", ), ( vRecur, vRecur(rscale="GREGORIAN", freq="YEARLY", skip=vSkip.FORWARD), b"RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD", ), ], ) def test_conversion(ty, recur, ics): """Test string conversion.""" assert recur.to_ical() == ics assert ty.from_ical(ics.decode()) == recur assert ty.from_ical(ics.decode()).to_ical() == ics icalendar-6.3.1/src/icalendar/tests/test_rfc_7986.py000066400000000000000000000134021501302773300221770ustar00rootroot00000000000000"""This tests additional attributes from :rfc:`7986`. Some attributes are also available as ``X-*`` attributes. They are also considered. """ from __future__ import annotations from typing import Union import pytest from icalendar import Calendar, Event, Journal, Todo from icalendar.prop import vText @pytest.fixture() def calendar() -> Calendar: """Empty calendar""" return Calendar() param_name = pytest.mark.parametrize("name", ["Company Vacation Days", "Calendar Name"]) param_prop = pytest.mark.parametrize("prop", ["NAME", "X-WR-CALNAME"]) @param_prop @param_name def test_get_calendar_name(prop, name, calendar): """Get the name of the calendar.""" calendar.add(prop, name) assert calendar.calendar_name == name @param_name def test_set_calendar_name(name, calendar): """Setting the name overrides the old attributes.""" calendar.calendar_name = name assert calendar.calendar_name == name assert calendar["NAME"] == name @param_name @param_prop def test_replace_name(name, prop, calendar): """Setting the name overrides the old attributes.""" calendar[prop] = "Other Name" calendar.calendar_name = name assert calendar.calendar_name == name @param_name @param_prop def test_del_name(name, calendar, prop): """Delete the name.""" calendar.add(prop, name) del calendar.calendar_name assert calendar.calendar_name is None def test_default_name(calendar): """We have no name by default.""" assert calendar.calendar_name is None @param_name def test_setting_the_name_deletes_the_non_standard_attribute(calendar, name): """The default_attr is deleted when setting the name.""" calendar["X-WR-CALNAME"] = name assert "X-WR-CALNAME" in calendar calendar.calendar_name = "other name" assert "X-WR-CALNAME" not in calendar @param_name @pytest.mark.parametrize("order", [1, 2]) def test_multiple_names_use_the_one_without_a_language(calendar, name, order): """Add several names and use the one without a language param.""" if order == 1: calendar.add("NAME", name) calendar.add("NAME", vText("Kalendername", params={"LANGUAGE":"de"})) if order == 2: calendar.add("NAME", name) assert calendar.calendar_name == name @param_name def test_name_is_preferred(calendar, name): """NAME is more important that X-WR-CALNAME""" calendar.add("NAME", name) calendar.add("X-WR-CALNAME", "asd") assert calendar.calendar_name == name # For description, we would use the same tests as name, but we also use the # same code, so it is all right. param_color = pytest.mark.parametrize("desc", ["DESCRIPTION", "X-WR-CALDESC"]) @param_color @param_name def test_description(calendar, desc, name): """Get the value""" calendar.add(desc, name) assert calendar.description == name # For color, we would use the same tests as name, but we also use the # same code, so it is all right. param_color = pytest.mark.parametrize("color_param", ["COLOR", "X-APPLE-CALENDAR-COLOR"]) @param_color def test_get_calendar_color(calendar, color_param, color): """Get the value""" calendar.add(color_param, color) assert calendar.color == color @param_color def test_delete_calendar_color(calendar, color_param, color): """Delete the value""" calendar.add(color_param, color) del calendar.color assert calendar.color == "" assert color_param not in calendar @param_color def test_set_calendar_color(calendar, color_param, color): """Set the color and it replaces what is there.""" calendar.add(color_param, "green") calendar.color = color assert calendar.color == color assert calendar["COLOR"] == color def test_get_COLOR_first(calendar, color): """We prefer COLOR over X-APPLE-CALENDAR-COLOR""" calendar.add("COLOR", color) calendar.add("X-APPLE-CALENDAR-COLOR", "green") assert calendar.color == color # The color of the event is a bit different # It only appears once and does not have a backup. @pytest.fixture(params=[Calendar, Event, Todo, Journal]) def color_component(request) -> Union[Calendar, Event, Todo, Journal]: """An empty component that should have a color attribute.""" return request.param() @pytest.fixture(params=["blue", "#123456"]) def color(request) -> str: """Return a color.""" return request.param def test_default_color(color_component: Union[Calendar, Event, Todo, Journal]): """There is no color by default.""" assert color_component.color == "" def test_set_the_color(color:str, color_component: Union[Calendar, Event, Todo, Journal]): """We set the value and get it.""" color_component.color = color assert color_component.color == color assert color_component["COLOR"] == color def test_replace_color(color:str, color_component: Union[Calendar, Event, Todo, Journal]): """Replace the color.""" color_component.color = "blue" color_component.color = color assert color_component.color == color assert color_component["COLOR"] == color def test_multiple_colors(color_component: Union[Calendar, Event, Todo, Journal]): """Add several colors and use the first one.""" color_component.add("COLOR", "blue") color_component.add("COLOR", "green") assert color_component.color == "blue" def test_delete_the_color(color_component: Union[Calendar, Event, Todo, Journal]): """Delete the color.""" color_component.color = "blue" del color_component.color assert "COLOR" not in color_component assert color_component.color == "" def test_set_if_multiple_colors(color: str, color_component: Union[Calendar, Event, Todo, Journal]): """Add several colors and use the first one.""" color_component.add("COLOR", "blue") color_component.add("COLOR", "green") color_component.color = color assert color_component.color == color icalendar-6.3.1/src/icalendar/tests/test_rfc_7986_categories.py000066400000000000000000000043051501302773300244060ustar00rootroot00000000000000"""This tests the compatibility with RFC 7529. CATEGORIES property See - https://github.com/collective/icalendar/issues/655 - https://www.rfc-editor.org/rfc/rfc7529.html """ from typing import Union import pytest from icalendar import Calendar, Event, Journal, Todo CTJE = Union[Calendar, Event, Journal, Todo] @pytest.fixture(params=[Event, Calendar, Todo, Journal]) def component(request): """An empty component with possible categories.""" return request.param() def test_no_categories_at_creation(component: CTJE): """An empty component has no categories.""" assert "CATEGORIES" not in component assert component.categories == [] def test_add_one_category(component: CTJE): """Add one category.""" component.add("categories", "Lecture") assert component.categories == ["Lecture"] def test_add_multiple_categories(component: CTJE): """Add categories.""" component.add("categories", ["Lecture", "Workshop"]) assert component.categories == ["Lecture", "Workshop"] def test_set_categories(component: CTJE): """Set categories.""" component.categories = ["Lecture", "Workshop"] assert component.categories == ["Lecture", "Workshop"] def test_modify_list(component: CTJE): """Modify the list and it still works.""" component.categories = categories = ["cat1"] categories.append("cat2") assert component.categories == ["cat1", "cat2"] def test_delete_categories(component: CTJE): """Delete categories.""" component.categories = ["Lecture", "Workshop"] del component.categories assert "CATEGORIES" not in component assert component.categories == [] def test_manage_add_append_remove_categories(component: CTJE): """Manage multiple categories by merging them, then append and remove a category from the resulting set.""" component.add("categories", ["c1", "c2"]) component.add("categories", ["c3", "c4"]) assert component.categories == ["c1", "c2", "c3", "c4"] component.categories.append("c5") assert component.categories == ["c1", "c2", "c3", "c4", "c5"] component.categories.remove("c2") assert component.categories == ["c1", "c3", "c4", "c5"] assert "c1,c3,c4,c5" in component.to_ical().decode() icalendar-6.3.1/src/icalendar/tests/test_rfc_9074.py000066400000000000000000000027051501302773300221710ustar00rootroot00000000000000"""Test the VALARM compatibility of RFC 9074. See https://www.rfc-editor.org/rfc/rfc9074.html and also https://github.com/collective/icalendar/issues/657 """ import pytest from icalendar.prop import vDDDTypes, vText @pytest.mark.parametrize( ("prop", "cls", "file", "alarm_index"), [ ("UID", vText, "rfc_9074_example_1", 0), ("RELATED-TO", vText, "rfc_9074_example_2", 1), ("ACKNOWLEDGED", vDDDTypes, "rfc_9074_example_3", 0), ("PROXIMITY", vText, "rfc_9074_example_proximity", 0), ], ) def test_calendar_types(events, prop, cls, file, alarm_index): """Check the types of the properties.""" event = events[file] alarm = event.subcomponents[alarm_index] value = alarm[prop] assert isinstance(value, cls) def test_snooze(events): """Check values of the alarms.""" alarm_1 = events.rfc_9074_example_3.subcomponents[0] assert alarm_1["ACKNOWLEDGED"].dt == vDDDTypes.from_ical("20210302T152024Z") alarm_2 = events.rfc_9074_example_3.subcomponents[1] assert alarm_2["RELATED-TO"] == "8297C37D-BA2D-4476-91AE-C1EAA364F8E1" assert alarm_2["RELATED-TO"].params["RELTYPE"] == "SNOOZE" def test_proximity(events): """Check the proximity values.""" alarm = events.rfc_9074_example_proximity.subcomponents[0] assert alarm["PROXIMITY"] == "DEPART" assert len(alarm.subcomponents) == 1 location = alarm.subcomponents[0] assert location["UID"] == "123456-abcdef-98765432" icalendar-6.3.1/src/icalendar/tests/test_time.py000066400000000000000000000015631501302773300216730ustar00rootroot00000000000000import datetime import os import icalendar def test_value_type_is_not_mapped(): """Usually, the value should be absent.""" assert "X-SOMETIME" not in icalendar.cal.types_factory.types_map def test_value_type_is_mapped(x_sometime): """The value is mapped for the test.""" assert "X-SOMETIME" in icalendar.cal.types_factory.types_map def test_create_from_ical(x_sometime): directory = os.path.dirname(__file__) ics = open(os.path.join(directory, "calendars", "time.ics"), "rb") cal = icalendar.Calendar.from_ical(ics.read()) ics.close() assert cal["X-SOMETIME"].dt == datetime.time(17, 20, 10) assert cal["X-SOMETIME"].to_ical() == "172010" def test_create_to_ical(x_sometime): cal = icalendar.Calendar() cal.add("X-SOMETIME", datetime.time(17, 20, 10)) assert b"X-SOMETIME;VALUE=TIME:172010" in cal.to_ical().splitlines() icalendar-6.3.1/src/icalendar/tests/test_timezone_identification.py000066400000000000000000000022341501302773300256340ustar00rootroot00000000000000"""Test that we can identify all timezones. Timezones can be removed from ./timezone_ids.py if they make the tests fail: Timezone information changes over time and can be dependent on the operating system's timezone database (zoneinfo, dateutil) or the package (pytz). We want to make sure we can roughly identify most of them. """ from icalendar.timezone import tzid_from_tzinfo, tzids_from_tzinfo from icalendar.timezone.tzp import TZP def test_can_identify_zoneinfo(tzid, zoneinfo_only, tzp:TZP): """Check that all those zoneinfo timezones can be identified.""" assert tzid in tzids_from_tzinfo(tzp.timezone(tzid)) def test_can_identify_pytz(tzid, pytz_only, tzp:TZP): """Check that all those pytz timezones can be identified.""" assert tzid in tzids_from_tzinfo(tzp.timezone(tzid)) def test_can_identify_dateutil(tzid): """Check that all those dateutil timezones can be identified.""" from dateutil.tz import gettz assert tzid in tzids_from_tzinfo(gettz(tzid)) def test_utc_is_identified(utc): """Test UTC because it is handled in a special way.""" assert "UTC" in tzids_from_tzinfo(utc) assert tzid_from_tzinfo(utc) == "UTC" icalendar-6.3.1/src/icalendar/tests/test_timezoned.py000066400000000000000000000437401501302773300227360ustar00rootroot00000000000000import datetime import dateutil.parser import icalendar from icalendar.prop import tzid_from_dt def test_create_from_ical(calendars, other_tzp): """Create a calendar from a .ics file.""" cal = calendars.timezoned assert cal["prodid"].to_ical() == b"-//Plone.org//NONSGML plone.app.event//EN" timezones = cal.walk("VTIMEZONE") assert len(timezones) == 1 tz = timezones[0] assert tz["tzid"].to_ical() == b"Europe/Vienna" std = tz.walk("STANDARD")[0] assert std.decoded("TZOFFSETFROM") == datetime.timedelta(0, 7200) ev1 = cal.walk("VEVENT")[0] assert ev1.decoded("DTSTART") == other_tzp.localize( datetime.datetime(2012, 2, 13, 10, 0, 0), "Europe/Vienna" ) assert ev1.decoded("DTSTAMP") == other_tzp.localize( datetime.datetime(2010, 10, 10, 9, 10, 10), "UTC" ) def test_create_to_ical(tzp): cal = icalendar.Calendar() cal.add("prodid", "-//Plone.org//NONSGML plone.app.event//EN") cal.add("version", "2.0") cal.add("x-wr-calname", "test create calendar") cal.add("x-wr-caldesc", "icalendar tests") cal.add("x-wr-relcalid", "12345") cal.add("x-wr-timezone", "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() event.add( "dtstart", tzp.localize(datetime.datetime(2012, 2, 13, 10, 00, 00), "Europe/Vienna"), ) event.add( "dtend", tzp.localize(datetime.datetime(2012, 2, 17, 18, 00, 00), "Europe/Vienna"), ) event.add( "dtstamp", tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna"), ) event.add( "created", tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna"), ) event.add("uid", "123456") event.add( "last-modified", tzp.localize(datetime.datetime(2010, 10, 10, 10, 10, 10), "Europe/Vienna"), ) event.add("summary", "artsprint 2012") # event.add('rrule', 'FREQ=YEARLY;INTERVAL=1;COUNT=10') event.add("description", "sprinting at the artsprint") event.add("location", "aka bild, wien") event.add("categories", "first subject") event.add("categories", "second subject") event.add("attendee", "häns") event.add("attendee", "franz") event.add("attendee", "sepp") event.add("contact", "Max Mustermann, 1010 Wien") event.add("url", "https://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: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:19700329T" "020000|TZNAME:CEST|TZOFFSETFROM:+0100|TZOFFSETTO:+0200|END:DAYLI" "GHT|END:VTIMEZONE" assert vtimezone_lines in test_out test_str = "DTSTART;TZID=Europe/Vienna:20120213T100000" assert test_str in test_out assert "ATTENDEE:sepp" in test_out # ical standard expects DTSTAMP and CREATED in UTC assert "DTSTAMP:20101010T081010Z" in test_out assert "CREATED:20101010T081010Z" in test_out def test_tzinfo_dateutil(): """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") assert date.tzinfo.__module__.startswith("dateutil.tz") assert date2.tzinfo.__module__.startswith("dateutil.tz") # make sure, it's parsed properly and doesn't throw an error assert icalendar.vDDDTypes(date).to_ical() == b"20120830T224100Z" assert icalendar.vDDDTypes(date2).to_ical() == b"20120830T224100" def test_create_america_new_york(calendars, tzp): """testing America/New_York, the most complex example from the RFC""" cal = calendars.america_new_york dt = cal.events[0].start assert tzid_from_dt(dt) in ("custom_America/New_York", "EDT") def test_create_america_new_york_forward_reference(calendars, tzp): """testing America/New_York variant with VTIMEZONE as a forward reference""" cal = calendars.america_new_york_forward_reference dt = cal.walk('VEVENT')[0]['DTSTART'][0].dt assert tzid_from_dt(dt) in ('custom_America/New_York_Forward_reference', "EDT") def test_america_new_york_with_pytz(calendars, tzp, pytz_only): """Create a custom timezone with pytz and test the transition times.""" print(tzp) cal = calendars.america_new_york dt = cal.events[0].start tz = dt.tzinfo tz_new_york = tzp.timezone("America/New_York") # for reasons (tm) the locally installed version of the timezone # database isn't always complete, therefore we only compare some # transition times ny_transition_times = [] ny_transition_info = [] for num, date in enumerate(tz_new_york._utc_transition_times): if ( datetime.datetime(1967, 4, 30, 7, 0) <= date <= datetime.datetime(2037, 11, 1, 6, 0) ): ny_transition_times.append(date) ny_transition_info.append(tz_new_york._transition_info[num]) assert tz._utc_transition_times[:142] == ny_transition_times assert tz._transition_info[0:142] == ny_transition_info assert ( datetime.timedelta(-1, 72000), datetime.timedelta(0, 3600), "EDT", ) in tz._tzinfos.keys() assert ( datetime.timedelta(-1, 68400), datetime.timedelta(0), "EST", ) in tz._tzinfos.keys() fiji_transition_times = [ datetime.datetime(1915, 10, 25, 12, 4), datetime.datetime(1998, 10, 31, 14, 0), datetime.datetime(1999, 2, 27, 14, 0), datetime.datetime(1999, 11, 6, 14, 0), datetime.datetime(2000, 2, 26, 14, 0), datetime.datetime(2009, 11, 28, 14, 0), datetime.datetime(2010, 3, 27, 14, 0), datetime.datetime(2010, 10, 23, 14, 0), datetime.datetime(2011, 3, 5, 14, 0), datetime.datetime(2011, 10, 22, 14, 0), datetime.datetime(2012, 1, 21, 14, 0), datetime.datetime(2012, 10, 20, 14, 0), datetime.datetime(2013, 1, 19, 14, 0), datetime.datetime(2013, 10, 26, 14, 0), datetime.datetime(2014, 1, 18, 13, 0), datetime.datetime(2014, 10, 25, 14, 0), datetime.datetime(2015, 1, 17, 13, 0), datetime.datetime(2015, 10, 24, 14, 0), datetime.datetime(2016, 1, 23, 13, 0), datetime.datetime(2016, 10, 22, 14, 0), datetime.datetime(2017, 1, 21, 13, 0), datetime.datetime(2017, 10, 21, 14, 0), datetime.datetime(2018, 1, 20, 13, 0), datetime.datetime(2018, 10, 20, 14, 0), datetime.datetime(2019, 1, 19, 13, 0), datetime.datetime(2019, 10, 26, 14, 0), datetime.datetime(2020, 1, 18, 13, 0), datetime.datetime(2020, 10, 24, 14, 0), datetime.datetime(2021, 1, 23, 13, 0), datetime.datetime(2021, 10, 23, 14, 0), datetime.datetime(2022, 1, 22, 13, 0), datetime.datetime(2022, 10, 22, 14, 0), datetime.datetime(2023, 1, 21, 13, 0), datetime.datetime(2023, 10, 21, 14, 0), datetime.datetime(2024, 1, 20, 13, 0), datetime.datetime(2024, 10, 26, 14, 0), datetime.datetime(2025, 1, 18, 13, 0), datetime.datetime(2025, 10, 25, 14, 0), datetime.datetime(2026, 1, 17, 13, 0), datetime.datetime(2026, 10, 24, 14, 0), datetime.datetime(2027, 1, 23, 13, 0), datetime.datetime(2027, 10, 23, 14, 0), datetime.datetime(2028, 1, 22, 13, 0), datetime.datetime(2028, 10, 21, 14, 0), datetime.datetime(2029, 1, 20, 13, 0), datetime.datetime(2029, 10, 20, 14, 0), datetime.datetime(2030, 1, 19, 13, 0), datetime.datetime(2030, 10, 26, 14, 0), datetime.datetime(2031, 1, 18, 13, 0), datetime.datetime(2031, 10, 25, 14, 0), datetime.datetime(2032, 1, 17, 13, 0), datetime.datetime(2032, 10, 23, 14, 0), datetime.datetime(2033, 1, 22, 13, 0), datetime.datetime(2033, 10, 22, 14, 0), datetime.datetime(2034, 1, 21, 13, 0), datetime.datetime(2034, 10, 21, 14, 0), datetime.datetime(2035, 1, 20, 13, 0), datetime.datetime(2035, 10, 20, 14, 0), datetime.datetime(2036, 1, 19, 13, 0), datetime.datetime(2036, 10, 25, 14, 0), datetime.datetime(2037, 1, 17, 13, 0), datetime.datetime(2037, 10, 24, 14, 0), datetime.datetime(2038, 1, 23, 13, 0), datetime.datetime(2038, 10, 23, 14, 0), ] fiji_transition_info = ( [ ( datetime.timedelta(0, 43200), datetime.timedelta(0), "custom_Pacific/Fiji_19151026T000000_+115544_+1200", ) ] + 3 * [ ( datetime.timedelta(0, 46800), datetime.timedelta(0, 3600), "custom_Pacific/Fiji_19981101T020000_+1200_+1300", ), ( datetime.timedelta(0, 43200), datetime.timedelta(0), "custom_Pacific/Fiji_19990228T030000_+1300_+1200", ), ] + 3 * [ ( datetime.timedelta(0, 46800), datetime.timedelta(0, 3600), "custom_Pacific/Fiji_20101024T020000_+1200_+1300", ), ( datetime.timedelta(0, 43200), datetime.timedelta(0), "custom_Pacific/Fiji_19990228T030000_+1300_+1200", ), ] + 25 * [ ( datetime.timedelta(0, 46800), datetime.timedelta(0, 3600), "custom_Pacific/Fiji_20101024T020000_+1200_+1300", ), ( datetime.timedelta(0, 43200), datetime.timedelta(0), "custom_Pacific/Fiji_20140119T020000_+1300_+1200", ), ] + [ ( datetime.timedelta(0, 46800), datetime.timedelta(0, 3600), "custom_Pacific/Fiji_20101024T020000_+1200_+1300", ) ] ) def test_create_pacific_fiji(calendars, pytz_only): """testing Pacific/Fiji, another pretty complex example with more than one RDATE property per subcomponent""" cal = calendars.pacific_fiji tz = cal.walk("VEVENT")[0]["DTSTART"][0].dt.tzinfo assert str(tz) == "custom_Pacific/Fiji" assert tz._utc_transition_times == fiji_transition_times assert tz._transition_info == fiji_transition_info assert ( datetime.timedelta(0, 46800), datetime.timedelta(0, 3600), "custom_Pacific/Fiji_19981101T020000_+1200_+1300", ) in tz._tzinfos.keys() assert ( datetime.timedelta(0, 43200), datetime.timedelta(0), "custom_Pacific/Fiji_19990228T030000_+1300_+1200", ) in tz._tzinfos.keys() # these are the expected offsets before and after the fiji_transition_times fiji_expected_offsets = [ (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), (datetime.timedelta(hours=13), datetime.timedelta(hours=13)), (datetime.timedelta(hours=12), datetime.timedelta(hours=12)), ] def test_transition_times_fiji(tzp, timezones): """The transition times are computed.""" tz = timezones.pacific_fiji.to_tz(tzp, lookup_tzid=False) offsets = [] # [(before, after), ...] for i, transition_time in enumerate(fiji_transition_times): before_after_offset = [] for offset in (datetime.timedelta(hours=-1), datetime.timedelta(hours=+1)): time_in_timezone = tzp.localize(transition_time + offset, tz) utc_offset = time_in_timezone.utcoffset() before_after_offset.append(utc_offset) offsets.append(tuple(before_after_offset)) assert offsets == fiji_expected_offsets def test_same_start_date(calendars): """testing if we can handle VTIMEZONEs whose different components have the same start DTIMEs.""" cal = calendars.timezone_same_start d = cal.subcomponents[1]["DTSTART"].dt assert d.strftime("%c") == "Fri Feb 24 12:00:00 2017" def test_same_start_date_and_offset(calendars): """testing if we can handle VTIMEZONEs whose different components have the same DTSTARTs, TZOFFSETFROM, and TZOFFSETTO.""" cal = calendars.timezone_same_start_and_offset d = cal.subcomponents[1]["DTSTART"].dt assert d.strftime("%c") == "Fri Feb 24 12:00:00 2017" def test_rdate(calendars): """testing if we can handle VTIMEZONEs who only have an RDATE, not RRULE""" cal = calendars.timezone_rdate vevent = cal.walk("VEVENT")[0] assert tzid_from_dt(vevent["DTSTART"].dt) in ("posix/Europe/Vaduz", "CET") def test_rdate_pytz(calendars, pytz_only): """testing if we can handle VTIMEZONEs who only have an RDATE, not RRULE""" cal = calendars.timezone_rdate vevent = cal.walk("VEVENT")[0] tz = vevent["DTSTART"].dt.tzinfo assert tz._utc_transition_times[:6] == [ datetime.datetime(1901, 12, 13, 20, 45, 38), datetime.datetime(1941, 5, 5, 0, 0, 0), datetime.datetime(1941, 10, 6, 0, 0, 0), datetime.datetime(1942, 5, 4, 0, 0, 0), datetime.datetime(1942, 10, 5, 0, 0, 0), datetime.datetime(1981, 3, 29, 1, 0), ] assert tz._transition_info[:6] == [ (datetime.timedelta(0, 3600), datetime.timedelta(0), "CET"), (datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), "CEST"), (datetime.timedelta(0, 3600), datetime.timedelta(0), "CET"), (datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), "CEST"), (datetime.timedelta(0, 3600), datetime.timedelta(0), "CET"), (datetime.timedelta(0, 7200), datetime.timedelta(0, 3600), "CEST"), ] icalendar-6.3.1/src/icalendar/tests/test_unit_cal.py000066400000000000000000000403111501302773300225250ustar00rootroot00000000000000import itertools import re from datetime import datetime, timedelta import pytest import icalendar from icalendar import prop from icalendar.cal import Calendar, Component, Event from icalendar.prop import tzid_from_dt def test_cal_Component(calendar_component): """A component is like a dictionary with extra methods and attributes.""" assert calendar_component assert calendar_component.is_empty() def test_nonempty_calendar_component(calendar_component): """Every key defines a property.A property can consist of either a single item. This can be set with a single value... """ calendar_component["prodid"] = "-//max m//icalendar.mxm.dk/" assert not calendar_component.is_empty() assert calendar_component == Calendar({"PRODID": "-//max m//icalendar.mxm.dk/"}) # or with a list calendar_component["ATTENDEE"] = ["Max M", "Rasmussen"] assert calendar_component == Calendar( {"ATTENDEE": ["Max M", "Rasmussen"], "PRODID": "-//max m//icalendar.mxm.dk/"} ) def test_add_multiple_values(event_component): """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. """ # add multiple values at once event_component.add("attendee", ["test@test.com", "test2@test.com"]) # or add one per line event_component.add("attendee", "maxm@mxm.dk") event_component.add("attendee", "test@example.dk") # add again multiple values at once to very concatenaton of lists event_component.add("attendee", ["test3@test.com", "test4@test.com"]) assert event_component == 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"), ] } ) def test_get_content_directly(c): """You can get the values back directly ...""" c.add("prodid", "-//my product//") assert c["prodid"] == prop.vText("-//my product//") # ... or decoded to a python type assert c.decoded("prodid") == b"-//my product//" def test_get_default_value(c): """With default values for non existing properties""" assert c.decoded("version", "No Version") == "No Version" def test_default_list_example(c): c.add("rdate", [datetime(2013, 3, 28), datetime(2013, 3, 27)]) assert isinstance(c.decoded("rdate"), prop.vDDDLists) def test_render_component(calendar_component): """The component can render itself in the RFC 5545 format.""" calendar_component.add("attendee", "Max M") assert ( calendar_component.to_ical() == b"BEGIN:VCALENDAR\r\nATTENDEE:Max M\r\nEND:VCALENDAR\r\n" ) def test_nested_component_event_ics(filled_event_component): """Check the ical string of the event component.""" assert filled_event_component.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" ) def test_nested_components(calendar_component, filled_event_component): """Components can be nested, so You can add a subcomponent. Eg a calendar holds events.""" self.assertEqual( calendar_component.subcomponents, [ Event( { "DTEND": "20000102T000000", "DTSTART": "20000101T000000", "SUMMARY": "A brief history of time", } ) ], ) def test_walk_filled_calendar_component(calendar_component, filled_event_component): """We can walk over nested componentes with the walk method.""" assert [i.name for i in calendar_component.walk()] == ["VCALENDAR", "VEVENT"] def test_filter_walk(calendar_component, filled_event_component): """We can also just walk over specific component types, by filtering them on their name.""" assert [i.name for i in calendar_component.walk("VEVENT")] == ["VEVENT"] assert [i["dtstart"] for i in calendar_component.walk("VEVENT")] == [ "20000101T000000" ] def test_recursive_property_items(calendar_component, filled_event_component): """We can enumerate property items recursively with the property_items method.""" calendar_component.add("attendee", "Max M") assert calendar_component.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"), ] def test_flat_property_items(calendar_component, filled_event_component): """We can also enumerate property items just under the component.""" assert calendar_component.property_items(recursive=False) == [ ("BEGIN", b"VCALENDAR"), ("ATTENDEE", prop.vCalAddress("Max M")), ("END", b"VCALENDAR"), ] def test_flat_property_items(filled_event_component): """Flat enumeration on the event.""" assert filled_event_component.property_items(recursive=False) == [ ("BEGIN", b"VEVENT"), ("DTEND", "20000102T000000"), ("DTSTART", "20000101T000000"), ("SUMMARY", "A brief history of time"), ("END", b"VEVENT"), ] def test_indent(): """Text fields which span multiple mulitple lines require proper indenting""" c = Calendar() c["description"] = "Paragraph one\n\nParagraph two" assert c.to_ical() == ( b"BEGIN:VCALENDAR\r\nDESCRIPTION:Paragraph one\\n\\nParagraph two" + b"\r\nEND:VCALENDAR\r\n" ) def test_INLINE_properties(calendar_with_resources): """INLINE properties have their values on one property line. Note the double quoting of the value with a colon in it. """ assert calendar_with_resources == Calendar( {"RESOURCES": 'Chair, Table, "Room: 42"'} ) assert calendar_with_resources.to_ical() == ( b'BEGIN:VCALENDAR\r\nRESOURCES:Chair\\, Table\\, "Room: 42"\r\n' + b"END:VCALENDAR\r\n" ) def test_get_inline(calendar_with_resources): """The inline values must be handled by the get_inline() and set_inline() methods. """ assert calendar_with_resources.get_inline("resources", decode=0) == [ "Chair", "Table", "Room: 42", ] def test_get_inline_decoded(calendar_with_resources): """These can also be decoded""" assert calendar_with_resources.get_inline("resources", decode=1) == [ b"Chair", b"Table", b"Room: 42", ] def test_set_inline(calendar_with_resources): """You can set them directly ...""" calendar_with_resources.set_inline( "resources", ["A", "List", "of", "some, recources"], encode=1 ) assert calendar_with_resources["resources"] == 'A,List,of,"some, recources"' assert calendar_with_resources.get_inline("resources", decode=0) == [ "A", "List", "of", "some, recources", ] def test_inline_free_busy_inline(c): c["freebusy"] = ( "19970308T160000Z/PT3H,19970308T200000Z/PT1H," + "19970308T230000Z/19970309T000000Z" ) assert c.get_inline("freebusy", decode=0) == [ "19970308T160000Z/PT3H", "19970308T200000Z/PT1H", "19970308T230000Z/19970309T000000Z", ] freebusy = c.get_inline("freebusy", decode=1) assert isinstance(freebusy[0][0], datetime) assert isinstance(freebusy[0][1], timedelta) def test_cal_Component_add(comp, tzp): """Test the for timezone correctness: dtstart should preserve it's timezone, created, dtstamp and last-modified must be in UTC. """ comp.add("dtstart", tzp.localize(datetime(2010, 10, 10, 10, 0, 0), "Europe/Vienna")) comp.add("created", datetime(2010, 10, 10, 12, 0, 0)) comp.add("dtstamp", tzp.localize(datetime(2010, 10, 10, 14, 0, 0), "Europe/Vienna")) comp.add("last-modified", tzp.localize_utc(datetime(2010, 10, 10, 16, 0, 0))) lines = comp.to_ical().splitlines() assert b"DTSTART;TZID=Europe/Vienna:20101010T100000" in lines assert b"CREATED:20101010T120000Z" in lines assert b"DTSTAMP:20101010T120000Z" in lines assert b"LAST-MODIFIED:20101010T160000Z" in lines def test_cal_Component_add_no_reencode(comp): """Already encoded values should not be re-encoded.""" comp.add("ATTACH", "me") comp.add("ATTACH", "you", encode=False) binary = prop.vBinary("us") comp.add("ATTACH", binary) assert comp["ATTACH"] == ["me", "you", binary] def test_cal_Component_add_property_parameter(comp): """Test the for timezone correctness: dtstart should preserve it's timezone, crated, dtstamp and last-modified must be in UTC. """ comp.add("X-TEST-PROP", "tryout.", parameters={"prop1": "val1", "prop2": "val2"}) lines = comp.to_ical().splitlines() assert b"X-TEST-PROP;PROP1=val1;PROP2=val2:tryout." in lines comp_prop = pytest.mark.parametrize( "component_name, property_name", [ ("VEVENT", "DTSTART"), ("VEVENT", "DTEND"), ("VEVENT", "RECURRENCE-ID"), ("VTODO", "DUE"), ], ) @comp_prop def test_cal_Component_from_ical(component_name, property_name, tzp): """Check for proper handling of TZID parameter of datetime properties""" 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) assert tzid_from_dt(component[property_name].dt) == "America/Denver" @comp_prop def test_cal_Component_from_ical_2(component_name, property_name, tzp): """Check for proper handling of TZID parameter of datetime properties""" component_str = "BEGIN:" + component_name + "\n" component_str += property_name + ":" component_str += "20120404T073000\nEND:" + component_name component = Component.from_ical(component_str) assert component[property_name].dt.tzinfo == None def test_cal_Component_to_ical_property_order(): component_str = [ b"BEGIN:VEVENT", b"DTSTART:19970714T170000Z", b"DTEND:19970715T035959Z", b"SUMMARY:Bastille Day Party", b"END:VEVENT", ] component = Component.from_ical(b"\r\n".join(component_str)) sorted_str = component.to_ical().splitlines() assert sorted_str != component_str assert set(sorted_str) == set(component_str) preserved_str = component.to_ical(sorted=False).splitlines() assert preserved_str == component_str def test_cal_Component_to_ical_parameter_order(): component_str = [ b"BEGIN:VEVENT", b"X-FOOBAR;C=one;A=two;B=three:helloworld.", b"END:VEVENT", ] component = Component.from_ical(b"\r\n".join(component_str)) sorted_str = component.to_ical().splitlines() assert sorted_str[0] == component_str[0] assert sorted_str[1] == b"X-FOOBAR;A=two;B=three;C=one:helloworld." assert sorted_str[2] == component_str[2] preserved_str = component.to_ical(sorted=False).splitlines() assert preserved_str == component_str @pytest.fixture def repr_example(c): class ReprExample: component = c component["key1"] = "value1" calendar = Calendar() calendar["key1"] = "value1" event = Event() event["key1"] = "value1" nested = Component(key1="VALUE1") nested.add_component(component) nested.add_component(calendar) return ReprExample def test_repr_component(repr_example): """Test correct class representation.""" assert re.match(r"Component\({u?'KEY1': u?'value1'}\)", str(repr_example.component)) def test_repr_calendar(repr_example): assert re.match(r"VCALENDAR\({u?'KEY1': u?'value1'}\)", str(repr_example.calendar)) def test_repr_event(repr_example): assert re.match(r"VEVENT\({u?'KEY1': u?'value1'}\)", str(repr_example.event)) def test_nested_components(repr_example): """Representation of nested Components""" repr_example.calendar.add_component(repr_example.event) print(repr_example.nested) assert re.match( r"Component\({u?'KEY1': u?'VALUE1'}, " r"Component\({u?'KEY1': u?'value1'}\), " r"VCALENDAR\({u?'KEY1': u?'value1'}, " r"VEVENT\({u?'KEY1': u?'value1'}\)\)\)", str(repr_example.nested), ) def test_component_factory_VEVENT(factory): """Check the events in the component factory""" component = factory["VEVENT"] event = component(dtstart="19700101") assert event.to_ical() == b"BEGIN:VEVENT\r\nDTSTART:19700101\r\nEND:VEVENT\r\n" def test_component_factory_VCALENDAR(factory): """Check the VCALENDAR in the factory.""" assert factory.get("VCALENDAR") == icalendar.cal.Calendar def test_minimal_calendar_component_with_one_event(): """Setting up a minimal calendar component looks like this""" 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 = Event() event["summary"] = "Python meeting about calendaring" event["uid"] = "42" event.add("dtstart", datetime(2005, 4, 4, 8, 0, 0)) cal.add_component(event) assert ( cal.subcomponents[0].to_ical() == b"BEGIN:VEVENT\r\nSUMMARY:Python meeting about calendaring\r\n" + b"DTSTART:20050404T080000\r\nUID:42\r\n" + b"END:VEVENT\r\n" ) def test_calendar_with_parsing_errors_includes_all_events(calendars): """Parsing a complete calendar from a string will silently ignore wrong events but adding the error information to the component's 'errors' attribute. The error in the following is the third EXDATE: it has an empty DATE. """ event_descriptions = [ e["DESCRIPTION"].to_ical() for e in calendars.parsing_error.walk("VEVENT") ] assert event_descriptions == [b"Perfectly OK event", b"Wrong event"] def test_calendar_with_parsing_errors_has_an_error_in_one_event(calendars): """Parsing a complete calendar from a string will silently ignore wrong events but adding the error information to the component's 'errors' attribute. The error in the following is the third EXDATE: it has an empty DATE. """ errors = [e.errors for e in calendars.parsing_error.walk("VEVENT")] assert errors == [[], [("EXDATE", "Expected datetime, date, or time, got: ''")]] def test_cal_strict_parsing(calendars): """If components are damaged, we raise an exception.""" with pytest.raises(ValueError): calendars.parsing_error_in_UTC_offset def test_cal_ignore_errors_parsing(calendars, vUTCOffset_ignore_exceptions): """If we diable the errors, we should be able to put the calendar back together.""" assert ( calendars.parsing_error_in_UTC_offset.to_ical() == calendars.parsing_error_in_UTC_offset.raw_ics ) @pytest.mark.parametrize( ("calendar", "other_calendar"), itertools.product( [ "issue_156_RDATE_with_PERIOD_TZID_khal", "issue_156_RDATE_with_PERIOD_TZID_khal_2", "issue_178_custom_component_contains_other", "issue_178_custom_component_inside_other", "issue_526_calendar_with_events", "issue_526_calendar_with_different_events", "issue_526_calendar_with_event_subset", ], repeat=2, ), ) def test_comparing_calendars(calendars, calendar, other_calendar, tzp): are_calendars_equal = calendars[calendar] == calendars[other_calendar] are_calendars_actually_equal = calendar == other_calendar assert are_calendars_equal == are_calendars_actually_equal @pytest.mark.parametrize( ("calendar", "shuffeled_calendar"), [ ( "issue_526_calendar_with_events", "issue_526_calendar_with_shuffeled_events", ), ], ) def test_calendars_with_same_subcomponents_in_different_order_are_equal( calendars, calendar, shuffeled_calendar ): assert ( calendars[calendar].subcomponents != calendars[shuffeled_calendar].subcomponents ) assert calendars[calendar] == calendars[shuffeled_calendar] icalendar-6.3.1/src/icalendar/tests/test_unit_caselessdict.py000066400000000000000000000070301501302773300244350ustar00rootroot00000000000000import 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 = { "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_copy(self): CaselessDict = icalendar.caselessdict.CaselessDict original_dict = CaselessDict(key1="val1", key2="val2") copied_dict = original_dict.copy() self.assertEqual(original_dict, copied_dict) 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") assert ncd.has_key("key1") 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.assertIn("key4", ncd) del ncd["key4"] self.assertNotIn("key4", 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-6.3.1/src/icalendar/tests/test_unit_parser_tools.py000066400000000000000000000022461501302773300245070ustar00rootroot00000000000000import unittest from icalendar.parser_tools import data_encode, to_unicode, from_unicode class TestParserTools(unittest.TestCase): def test_parser_tools_to_unicode(self): self.assertEqual(to_unicode(b"spam"), "spam") self.assertEqual(to_unicode("spam"), "spam") self.assertEqual(to_unicode(b"spam"), "spam") self.assertEqual(to_unicode(b"\xc6\xb5"), "\u01b5") self.assertEqual(to_unicode(b"\xc6\xb5"), "\u01b5") self.assertEqual(to_unicode(b"\xc6\xb5", encoding="ascii"), "\u01b5") self.assertEqual(to_unicode(1), 1) self.assertIsNone(to_unicode(None)) def test_parser_tools_from_unicode(self): self.assertEqual(from_unicode("\u01b5", encoding="ascii"), b"\xc6\xb5") def test_parser_tools_data_encode(self): data1 = { "k1": "v1", "k2": "v2", "k3": "v3", "li1": ["it1", "it2", {"k4": "v4", "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-6.3.1/src/icalendar/tests/test_unit_tools.py000066400000000000000000000045221501302773300231320ustar00rootroot00000000000000import unittest import pytest 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.assertEqual(len(txt), length) self.assertIn(b"@example.com", txt) # You should at least insert your own hostname to be more compliant uid = g.uid("Example.ORG") txt = uid.to_ical() self.assertEqual(len(txt), length) self.assertIn(b"@Example.ORG", txt) # You can also insert a path or similar uid = g.uid("Example.ORG", "/path/to/content") txt = uid.to_ical() self.assertEqual(len(txt), length) self.assertIn(b"-/path/to/content@Example.ORG", txt) @pytest.mark.parametrize( ("split", "expected", "args", "kw"), [ # default argument host_name ( "@", "example.com", (), {}, ), ("@", "example.com", ("example.com",), {}), ("@", "example.com", (), {"host_name": "example.com"}), # replaced host_name ("@", "test.test", ("test.test",), {}), ("@", "test.test", (), {"host_name": "test.test"}), # replace unique ( "-", "123@example.com", (), {"unique": "123"}, ), ( "-", "abc@example.com", (), {"unique": "abc"}, ), # replace host_name and unique ( "-", "1234@test.icalendar", (), {"unique": "1234", "host_name": "test.icalendar"}, ), ( "-", "abc@test.example.com", ("test.example.com", "abc"), {}, ), ], ) def test_uid_generator_issue_345(args, kw, split, expected): """Issue #345 - Why is tools.UIDGenerator a class (that must be instantiated) instead of a module? see https://github.com/collective/icalendar/issues/345 """ uid = UIDGenerator.uid(*args, **kw) assert uid.split(split)[1] == expected def test_warning(): with pytest.warns(DeprecationWarning): UIDGenerator.uid() with pytest.warns(DeprecationWarning): UIDGenerator.rnd_string() icalendar-6.3.1/src/icalendar/tests/test_with_doctest.py000066400000000000000000000057201501302773300234340ustar00rootroot00000000000000"""This file tests the source code provided by the documentation. See - doctest documentation: https://docs.python.org/3/library/doctest.html - Issue 443: https://github.com/collective/icalendar/issues/443 This file should be tests, too: >>> print("Hello World!") Hello World! """ import doctest import importlib import os import sys import pytest HERE = os.path.dirname(__file__) or "." ICALENDAR_PATH = os.path.dirname(HERE) PYTHON_FILES = [ "/".join((dirpath, filename)) for dirpath, dirnames, filenames in os.walk(ICALENDAR_PATH) for filename in filenames if filename.lower().endswith(".py") and "fuzzing" not in dirpath ] MODULE_NAMES = [ "icalendar" + python_file[len(ICALENDAR_PATH) : -3].replace("\\", "/").replace("/", ".") for python_file in PYTHON_FILES ] def test_this_module_is_among_them(): assert __name__ in MODULE_NAMES @pytest.mark.parametrize("module_name", MODULE_NAMES) def test_docstring_of_python_file(module_name, env_for_doctest): """This test runs doctest on the Python module.""" try: module = importlib.import_module(module_name) except ModuleNotFoundError as e: if e.name == "pytz": pytest.skip("pytz is not installed, skipping this module.") raise test_result = doctest.testmod(module, name=module_name, globs=env_for_doctest) assert test_result.failed == 0, f"{test_result.failed} errors in {module_name}" # This collection needs to exclude .tox and other subdirectories DOCUMENTATION_PATH = os.path.join(HERE, "../../../") try: DOCUMENT_PATHS = [ os.path.join(DOCUMENTATION_PATH, subdir, filename) for subdir in ["docs", "."] for filename in os.listdir(os.path.join(DOCUMENTATION_PATH, subdir)) if filename.lower().endswith(".rst") ] except FileNotFoundError: raise OSError( "Could not find the documentation - remove the build folder and try again." ) @pytest.mark.parametrize( "filename", [ "README.rst", "index.rst", ], ) def test_files_is_included(filename): assert any(path.endswith(filename) for path in DOCUMENT_PATHS) @pytest.mark.parametrize("document", DOCUMENT_PATHS) def test_documentation_file(document, zoneinfo_only, env_for_doctest, tzp): """This test runs doctest on a documentation file. functions are also replaced to work. """ try: import pytz except ImportError: pytest.skip("pytz not installed, skipping this file.") try: # set raise_on_error to False if you wand to see the error for debug test_result = doctest.testfile( document, module_relative=False, globs=env_for_doctest, raise_on_error=False ) finally: tzp.use_zoneinfo() assert ( test_result.failed == 0 ), f"{test_result.failed} errors in {os.path.basename(document)}" def test_can_import_zoneinfo(env_for_doctest): """Allow importing zoneinfo for tests.""" assert "zoneinfo" in sys.modules icalendar-6.3.1/src/icalendar/tests/timezone_ids.py000066400000000000000000000317741501302773300223760ustar00rootroot00000000000000"""This file contains all the timezone ids that should be tested.""" TZIDS = [ "Africa/Abidjan", "Africa/Accra", "Africa/Addis_Ababa", "Africa/Algiers", "Africa/Asmara", "Africa/Asmera", "Africa/Bamako", "Africa/Bangui", "Africa/Banjul", "Africa/Bissau", "Africa/Blantyre", "Africa/Brazzaville", "Africa/Bujumbura", "Africa/Cairo", "Africa/Casablanca", "Africa/Ceuta", "Africa/Conakry", "Africa/Dakar", "Africa/Dar_es_Salaam", "Africa/Djibouti", "Africa/Douala", "Africa/El_Aaiun", "Africa/Freetown", "Africa/Gaborone", "Africa/Harare", "Africa/Johannesburg", "Africa/Juba", "Africa/Kampala", "Africa/Khartoum", "Africa/Kigali", "Africa/Kinshasa", "Africa/Lagos", "Africa/Libreville", "Africa/Lome", "Africa/Luanda", "Africa/Lubumbashi", "Africa/Lusaka", "Africa/Malabo", "Africa/Maputo", "Africa/Maseru", "Africa/Mbabane", "Africa/Mogadishu", "Africa/Monrovia", "Africa/Nairobi", "Africa/Ndjamena", "Africa/Niamey", "Africa/Nouakchott", "Africa/Ouagadougou", "Africa/Porto-Novo", "Africa/Sao_Tome", "Africa/Timbuktu", "Africa/Tripoli", "Africa/Tunis", "Africa/Windhoek", "America/Adak", "America/Anchorage", "America/Anguilla", "America/Antigua", "America/Araguaina", "America/Argentina/Buenos_Aires", "America/Argentina/Catamarca", "America/Argentina/ComodRivadavia", "America/Argentina/Cordoba", "America/Argentina/Jujuy", "America/Argentina/La_Rioja", "America/Argentina/Mendoza", "America/Argentina/Rio_Gallegos", "America/Argentina/Salta", "America/Argentina/San_Juan", "America/Argentina/San_Luis", "America/Argentina/Tucuman", "America/Argentina/Ushuaia", "America/Aruba", "America/Asuncion", "America/Atikokan", "America/Atka", "America/Bahia", "America/Bahia_Banderas", "America/Barbados", "America/Belem", "America/Belize", "America/Blanc-Sablon", "America/Boa_Vista", "America/Bogota", "America/Boise", "America/Buenos_Aires", "America/Cambridge_Bay", "America/Campo_Grande", "America/Cancun", "America/Caracas", "America/Catamarca", "America/Cayenne", "America/Cayman", "America/Chicago", "America/Chihuahua", "America/Ciudad_Juarez", "America/Coral_Harbour", "America/Cordoba", "America/Costa_Rica", "America/Creston", "America/Cuiaba", "America/Curacao", "America/Danmarkshavn", "America/Dawson", "America/Dawson_Creek", "America/Denver", "America/Detroit", "America/Dominica", "America/Edmonton", "America/Eirunepe", "America/El_Salvador", "America/Ensenada", "America/Fortaleza", "America/Fort_Nelson", "America/Fort_Wayne", "America/Glace_Bay", "America/Godthab", "America/Goose_Bay", "America/Grand_Turk", "America/Grenada", "America/Guadeloupe", "America/Guatemala", "America/Guayaquil", "America/Guyana", "America/Halifax", "America/Havana", "America/Hermosillo", "America/Indiana/Indianapolis", "America/Indiana/Knox", "America/Indiana/Marengo", "America/Indiana/Petersburg", "America/Indianapolis", "America/Indiana/Tell_City", "America/Indiana/Vevay", "America/Indiana/Vincennes", "America/Indiana/Winamac", "America/Inuvik", "America/Iqaluit", "America/Jamaica", "America/Jujuy", "America/Juneau", "America/Kentucky/Louisville", "America/Kentucky/Monticello", "America/Knox_IN", "America/Kralendijk", "America/La_Paz", "America/Lima", "America/Los_Angeles", "America/Louisville", "America/Lower_Princes", "America/Maceio", "America/Managua", "America/Manaus", "America/Marigot", "America/Martinique", "America/Matamoros", "America/Mazatlan", "America/Mendoza", "America/Menominee", "America/Metlakatla", "America/Mexico_City", "America/Miquelon", "America/Moncton", "America/Monterrey", "America/Montevideo", "America/Montreal", "America/Montserrat", "America/Nassau", "America/New_York", "America/Nipigon", "America/Nome", "America/Noronha", "America/North_Dakota/Beulah", "America/North_Dakota/Center", "America/North_Dakota/New_Salem", "America/Nuuk", "America/Ojinaga", "America/Panama", "America/Pangnirtung", "America/Paramaribo", "America/Phoenix", "America/Port-au-Prince", "America/Porto_Acre", "America/Port_of_Spain", "America/Porto_Velho", "America/Puerto_Rico", "America/Punta_Arenas", "America/Rainy_River", "America/Rankin_Inlet", "America/Recife", "America/Regina", "America/Resolute", "America/Rio_Branco", "America/Rosario", "America/Santa_Isabel", "America/Santarem", "America/Santiago", "America/Santo_Domingo", "America/Sao_Paulo", "America/Scoresbysund", "America/Shiprock", "America/Sitka", "America/St_Barthelemy", "America/St_Johns", "America/St_Kitts", "America/St_Lucia", "America/St_Thomas", "America/St_Vincent", "America/Swift_Current", "America/Tegucigalpa", "America/Thule", "America/Thunder_Bay", "America/Tijuana", "America/Toronto", "America/Tortola", "America/Vancouver", "America/Virgin", "America/Whitehorse", "America/Winnipeg", "America/Yakutat", "America/Yellowknife", "Antarctica/Casey", "Antarctica/Davis", "Antarctica/DumontDUrville", "Antarctica/Macquarie", "Antarctica/Mawson", "Antarctica/McMurdo", "Antarctica/Palmer", "Antarctica/Rothera", "Antarctica/South_Pole", "Antarctica/Syowa", "Antarctica/Troll", "Antarctica/Vostok", "Arctic/Longyearbyen", "Asia/Aden", "Asia/Almaty", "Asia/Amman", "Asia/Anadyr", "Asia/Aqtau", "Asia/Aqtobe", "Asia/Ashgabat", "Asia/Ashkhabad", "Asia/Atyrau", "Asia/Baghdad", "Asia/Bahrain", "Asia/Baku", "Asia/Bangkok", "Asia/Barnaul", "Asia/Beirut", "Asia/Bishkek", "Asia/Brunei", "Asia/Calcutta", "Asia/Chita", "Asia/Choibalsan", "Asia/Chongqing", "Asia/Chungking", "Asia/Colombo", "Asia/Dacca", "Asia/Damascus", "Asia/Dhaka", "Asia/Dili", "Asia/Dubai", "Asia/Dushanbe", "Asia/Famagusta", "Asia/Gaza", "Asia/Harbin", "Asia/Hebron", "Asia/Ho_Chi_Minh", "Asia/Hong_Kong", "Asia/Hovd", "Asia/Irkutsk", "Asia/Istanbul", "Asia/Jakarta", "Asia/Jayapura", "Asia/Jerusalem", "Asia/Kabul", "Asia/Kamchatka", "Asia/Karachi", "Asia/Kashgar", "Asia/Kathmandu", "Asia/Katmandu", "Asia/Khandyga", "Asia/Kolkata", "Asia/Krasnoyarsk", "Asia/Kuala_Lumpur", "Asia/Kuching", "Asia/Kuwait", "Asia/Macao", "Asia/Macau", "Asia/Magadan", "Asia/Makassar", "Asia/Manila", "Asia/Muscat", "Asia/Nicosia", "Asia/Novokuznetsk", "Asia/Novosibirsk", "Asia/Omsk", "Asia/Oral", "Asia/Phnom_Penh", "Asia/Pontianak", "Asia/Pyongyang", "Asia/Qatar", "Asia/Qostanay", "Asia/Qyzylorda", "Asia/Rangoon", "Asia/Riyadh", "Asia/Saigon", "Asia/Sakhalin", "Asia/Samarkand", "Asia/Seoul", "Asia/Shanghai", "Asia/Singapore", "Asia/Srednekolymsk", "Asia/Taipei", "Asia/Tashkent", "Asia/Tbilisi", "Asia/Tel_Aviv", "Asia/Thimbu", "Asia/Thimphu", "Asia/Tokyo", "Asia/Tomsk", "Asia/Ujung_Pandang", "Asia/Ulaanbaatar", "Asia/Ulan_Bator", "Asia/Urumqi", "Asia/Ust-Nera", "Asia/Vientiane", "Asia/Vladivostok", "Asia/Yakutsk", "Asia/Yangon", "Asia/Yekaterinburg", "Asia/Yerevan", "Atlantic/Azores", "Atlantic/Bermuda", "Atlantic/Canary", "Atlantic/Cape_Verde", "Atlantic/Faeroe", "Atlantic/Faroe", "Atlantic/Jan_Mayen", "Atlantic/Madeira", "Atlantic/Reykjavik", "Atlantic/South_Georgia", "Atlantic/Stanley", "Atlantic/St_Helena", "Australia/ACT", "Australia/Adelaide", "Australia/Brisbane", "Australia/Broken_Hill", "Australia/Canberra", "Australia/Currie", "Australia/Darwin", "Australia/Eucla", "Australia/Hobart", "Australia/LHI", "Australia/Lindeman", "Australia/Lord_Howe", "Australia/Melbourne", "Australia/North", "Australia/NSW", "Australia/Perth", "Australia/Queensland", "Australia/South", "Australia/Sydney", "Australia/Tasmania", "Australia/Victoria", "Australia/West", "Australia/Yancowinna", "Brazil/Acre", "Brazil/DeNoronha", "Brazil/East", "Brazil/West", "Canada/Atlantic", "Canada/Central", "Canada/Eastern", "Canada/Mountain", "Canada/Newfoundland", "Canada/Pacific", "Canada/Saskatchewan", "Canada/Yukon", "CET", "Chile/Continental", "Chile/EasterIsland", "CST6CDT", "Cuba", "EET", "Egypt", "Eire", "EST", "EST5EDT", "Etc/GMT", "Etc/GMT+0", "Etc/GMT-0", "Etc/GMT0", "Etc/GMT+1", "Etc/GMT-1", "Etc/GMT+10", "Etc/GMT-10", "Etc/GMT+11", "Etc/GMT-11", "Etc/GMT+12", "Etc/GMT-12", "Etc/GMT-13", "Etc/GMT-14", "Etc/GMT+2", "Etc/GMT-2", "Etc/GMT+3", "Etc/GMT-3", "Etc/GMT+4", "Etc/GMT-4", "Etc/GMT+5", "Etc/GMT-5", "Etc/GMT+6", "Etc/GMT-6", "Etc/GMT+7", "Etc/GMT-7", "Etc/GMT+8", "Etc/GMT-8", "Etc/GMT+9", "Etc/GMT-9", "Etc/Greenwich", "Etc/UCT", "Etc/Universal", "Etc/UTC", "Etc/Zulu", "Europe/Amsterdam", "Europe/Andorra", "Europe/Astrakhan", "Europe/Athens", "Europe/Belfast", "Europe/Belgrade", "Europe/Berlin", "Europe/Bratislava", "Europe/Brussels", "Europe/Bucharest", "Europe/Budapest", "Europe/Busingen", "Europe/Chisinau", "Europe/Copenhagen", "Europe/Dublin", "Europe/Gibraltar", "Europe/Guernsey", "Europe/Helsinki", "Europe/Isle_of_Man", "Europe/Istanbul", "Europe/Jersey", "Europe/Kaliningrad", "Europe/Kiev", "Europe/Kirov", "Europe/Kyiv", "Europe/Lisbon", "Europe/Ljubljana", "Europe/London", "Europe/Luxembourg", "Europe/Madrid", "Europe/Malta", "Europe/Mariehamn", "Europe/Minsk", "Europe/Monaco", "Europe/Moscow", "Europe/Nicosia", "Europe/Oslo", "Europe/Paris", "Europe/Podgorica", "Europe/Prague", "Europe/Riga", "Europe/Rome", "Europe/Samara", "Europe/San_Marino", "Europe/Sarajevo", "Europe/Saratov", "Europe/Simferopol", "Europe/Skopje", "Europe/Sofia", "Europe/Stockholm", "Europe/Tallinn", "Europe/Tirane", "Europe/Tiraspol", "Europe/Ulyanovsk", "Europe/Uzhgorod", "Europe/Vaduz", "Europe/Vatican", "Europe/Vienna", "Europe/Vilnius", "Europe/Volgograd", "Europe/Warsaw", "Europe/Zagreb", "Europe/Zaporozhye", "Europe/Zurich", "GB", "GB-Eire", "GMT", "GMT+0", "GMT-0", "GMT0", "Greenwich", "Hongkong", "HST", "Iceland", "Indian/Antananarivo", "Indian/Chagos", "Indian/Christmas", "Indian/Cocos", "Indian/Comoro", "Indian/Kerguelen", "Indian/Mahe", "Indian/Maldives", "Indian/Mauritius", "Indian/Mayotte", "Indian/Reunion", "Iran", "Asia/Tehran", "Israel", "Jamaica", "Japan", "Kwajalein", "Libya", "MET", "Mexico/BajaNorte", "Mexico/BajaSur", "Mexico/General", "MST", "MST7MDT", "Navajo", "NZ", "NZ-CHAT", "Pacific/Chatham", "Pacific/Apia", "Pacific/Auckland", "Pacific/Bougainville", "Pacific/Chuuk", "Pacific/Easter", "Pacific/Efate", "Pacific/Enderbury", "Pacific/Fakaofo", "Pacific/Fiji", "Pacific/Funafuti", "Pacific/Galapagos", "Pacific/Gambier", "Pacific/Guadalcanal", "Pacific/Guam", "Pacific/Honolulu", "Pacific/Johnston", "Pacific/Kanton", "Pacific/Kiritimati", "Pacific/Kosrae", "Pacific/Kwajalein", "Pacific/Majuro", "Pacific/Marquesas", "Pacific/Midway", "Pacific/Nauru", "Pacific/Niue", "Pacific/Norfolk", "Pacific/Noumea", "Pacific/Pago_Pago", "Pacific/Palau", "Pacific/Pitcairn", "Pacific/Pohnpei", "Pacific/Ponape", "Pacific/Port_Moresby", "Pacific/Rarotonga", "Pacific/Saipan", "Pacific/Samoa", "Pacific/Tahiti", "Pacific/Tarawa", "Pacific/Tongatapu", "Pacific/Truk", "Pacific/Wake", "Pacific/Wallis", "Pacific/Yap", "Poland", "Portugal", "PRC", "PST8PDT", "ROC", "ROK", "Singapore", "Turkey", "UCT", "Universal", "US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/Eastern", "US/East-Indiana", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa", "UTC", "WET", "W-SU", "Zulu", ] icalendar-6.3.1/src/icalendar/tests/timezones/000077500000000000000000000000001501302773300213345ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/tests/timezones/issue_237_brazilia_standard.ics000066400000000000000000000006201501302773300273120ustar00rootroot00000000000000BEGIN:VTIMEZONE TZID:(UTC-03:00) Brasília BEGIN:STANDARD TZNAME:Brasília standard DTSTART:16010101T235959 TZOFFSETFROM:-0200 TZOFFSETTO:-0300 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=3SA;BYMONTH=2 END:STANDARD BEGIN:DAYLIGHT TZNAME:Brasília daylight DTSTART:16010101T235959 TZOFFSETFROM:-0300 TZOFFSETTO:-0200 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SA;BYMONTH=10 END:DAYLIGHT END:VTIMEZONE icalendar-6.3.1/src/icalendar/tests/timezones/issue_53_tzid_parsed_properly.ics000066400000000000000000000006621501302773300300230ustar00rootroot00000000000000BEGIN: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 icalendar-6.3.1/src/icalendar/tests/timezones/issue_55_parse_error_on_utc_offset_with_seconds.ics000066400000000000000000000002741501302773300335710ustar00rootroot00000000000000BEGIN:VTIMEZONE TZID:America/Los Angeles BEGIN:STANDARD DTSTART:18831118T120702 RDATE:18831118T120702 TZNAME:PST TZOFFSETFROM:-075258 TZOFFSETTO:-0800 END:STANDARD END:VTIMEZONE icalendar-6.3.1/src/icalendar/tests/timezones/pacific_fiji.ics000066400000000000000000000016411501302773300244350ustar00rootroot00000000000000BEGIN:VTIMEZONE TZID:custom_Pacific/Fiji TZURL:http://tzurl.org/zoneinfo/Pacific/Fiji X-LIC-LOCATION:Pacific/Fiji BEGIN:DAYLIGHT TZOFFSETFROM:+1200 TZOFFSETTO:+1300 DTSTART:20101024T020000 RRULE:FREQ=YEARLY;BYMONTH=10;BYMONTHDAY=21,22,23,24,25,26,27;BYDAY=SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+1300 TZOFFSETTO:+1200 DTSTART:20140119T020000 RRULE:FREQ=YEARLY;BYMONTH=1;BYMONTHDAY=18,19,20,21,22,23,24;BYDAY=SU END:STANDARD BEGIN:STANDARD TZOFFSETFROM:+115544 TZOFFSETTO:+1200 DTSTART:19151026T000000 RDATE:19151026T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:+1200 TZOFFSETTO:+1300 DTSTART:19981101T020000 RDATE:19981101T020000 RDATE:19991107T020000 RDATE:20091129T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+1300 TZOFFSETTO:+1200 DTSTART:19990228T030000 RDATE:19990228T030000 RDATE:20000227T030000 RDATE:20100328T030000 RDATE:20110306T030000 RDATE:20120122T030000 RDATE:20130120T030000 END:STANDARD END:VTIMEZONE icalendar-6.3.1/src/icalendar/timezone/000077500000000000000000000000001501302773300200075ustar00rootroot00000000000000icalendar-6.3.1/src/icalendar/timezone/__init__.py000066400000000000000000000010451501302773300221200ustar00rootroot00000000000000"""This package contains all functionality for timezones.""" from .tzid import tzid_from_dt, tzid_from_tzinfo, tzids_from_tzinfo from .tzp import TZP tzp = TZP() def use_pytz(): """Use pytz as the implementation that looks up and creates timezones.""" tzp.use_pytz() def use_zoneinfo(): """Use zoneinfo as the implementation that looks up and creates timezones.""" tzp.use_zoneinfo() __all__ = [ "TZP", "tzp", "use_pytz", "use_zoneinfo", "tzid_from_tzinfo", "tzid_from_dt", "tzids_from_tzinfo", ] icalendar-6.3.1/src/icalendar/timezone/equivalent_timezone_ids.py000066400000000000000000000110711501302773300253070ustar00rootroot00000000000000"""This module helps identifying the timezone ids and where they differ. The algorithm: We use the tzname and the utcoffset for each hour from 1970 - 2030. We make a big map. If they are equivalent, they are equivalent within the time that is mostly used. You can regenerate the information from this module. See also: - https://stackoverflow.com/questions/79185519/which-timezones-are-equivalent Run this module: python -m icalendar.timezone.equivalent_timezone_ids """ from __future__ import annotations from collections import defaultdict from datetime import datetime, timedelta, tzinfo from pathlib import Path from pprint import pprint from typing import Callable, NamedTuple, Optional from pytz import AmbiguousTimeError, NonExistentTimeError from zoneinfo import ZoneInfo, available_timezones START = datetime(1970, 1, 1) # noqa: DTZ001 END = datetime(2020, 1, 1) # noqa: DTZ001 DISTANCE_FROM_TIMEZONE_CHANGE = timedelta(hours=12) DTS = [] dt = START while dt <= END: DTS.append(dt) dt += timedelta( hours=25 ) # This must be big enough to be fast and small enough to identify the timeszones before it is the present year del dt def main( create_timezones: list[Callable[[str], tzinfo]], name: str, ): """Generate a lookup table for timezone information if unknown timezones. We cannot create one lookup for all because they seem to be all equivalent if we mix timezone implementations. """ print(create_timezones, name) unsorted_tzids = available_timezones() unsorted_tzids.remove("localtime") unsorted_tzids.remove("Factory") class TZ(NamedTuple): tz: tzinfo id: str tzs = [ TZ(create_timezone(tzid), tzid) for create_timezone in create_timezones for tzid in unsorted_tzids ] def generate_tree( tzs: list[TZ], step: timedelta = timedelta(hours=1), start: datetime = START, end: datetime = END, todo: Optional[set[str]] = None, ) -> tuple[datetime, dict[timedelta, set[str]]] | set[str]: # should be recursive """Generate a lookup tree.""" if todo is None: todo = [tz.id for tz in tzs] print(f"{len(todo)} left to compute") print(len(tzs)) if len(tzs) == 0: raise ValueError("tzs cannot be empty") if len(tzs) == 1: todo.remove(tzs[0].id) return {tzs[0].id} while start < end: offsets: dict[timedelta, list[TZ]] = defaultdict(list) try: # if we are around a timezone change, we must move on # see https://github.com/collective/icalendar/issues/776 around_tz_change = not all( tz.tz.utcoffset(start) == tz.tz.utcoffset(start - DISTANCE_FROM_TIMEZONE_CHANGE) == tz.tz.utcoffset(start + DISTANCE_FROM_TIMEZONE_CHANGE) for tz in tzs ) except (NonExistentTimeError, AmbiguousTimeError): around_tz_change = True if around_tz_change: start += DISTANCE_FROM_TIMEZONE_CHANGE continue for tz in tzs: offsets[tz.tz.utcoffset(start)].append(tz) if len(offsets) == 1: start += step continue lookup = {} for offset, tzs in offsets.items(): lookup[offset] = generate_tree( tzs=tzs, step=step, start=start + step, end=end, todo=todo ) return start, lookup print(f"reached end with {len(tzs)} timezones - assuming they are equivalent.") result = set() for tz in tzs: result.add(tz.id) todo.remove(tz.id) return result lookup = generate_tree(tzs, step=timedelta(hours=33)) file = Path(__file__).parent / f"equivalent_timezone_ids_{name}.py" print(f"The result is written to {file}.") print("lookup = ", end="") pprint(lookup) with file.open("w") as f: f.write( f"'''This file is automatically generated by {Path(__file__).name}'''\n" ) f.write("import datetime\n\n") f.write("\nlookup = ") pprint(lookup, stream=f) f.write("\n\n__all__ = ['lookup']\n") return lookup __all__ = ["main"] if __name__ == "__main__": from dateutil.tz import gettz from pytz import timezone from zoneinfo import ZoneInfo # add more timezone implementations if you like main( [ZoneInfo, timezone, gettz], "result", ) icalendar-6.3.1/src/icalendar/timezone/equivalent_timezone_ids_result.py000066400000000000000000006544121501302773300267210ustar00rootroot00000000000000"""This file is automatically generated by equivalent_timezone_ids.py""" import datetime lookup = ( datetime.datetime(1970, 1, 2, 0, 0), { datetime.timedelta(days=-1, seconds=43200): ( datetime.datetime(1979, 10, 2, 0, 0), { datetime.timedelta(days=-1, seconds=43200): ( datetime.datetime(1993, 8, 22, 21, 0), { datetime.timedelta(days=-1, seconds=43200): {"Etc/GMT+12"}, datetime.timedelta(seconds=43200): { "Kwajalein", "Pacific/Kwajalein", }, }, ), datetime.timedelta(days=-1, seconds=46800): { "Pacific/Enderbury", "Pacific/Kanton", }, }, ), datetime.timedelta(days=-1, seconds=46800): ( datetime.datetime(1970, 4, 26, 15, 0), { datetime.timedelta(days=-1, seconds=46800): ( datetime.datetime(2010, 9, 26, 15, 0), { datetime.timedelta(days=-1, seconds=46800): ( datetime.datetime(2011, 12, 31, 21, 0), { datetime.timedelta(days=-1, seconds=46800): { "Etc/GMT+11", "Pacific/Midway", "Pacific/Niue", "Pacific/Pago_Pago", "Pacific/Samoa", "US/Samoa", }, datetime.timedelta(seconds=46800): {"Pacific/Fakaofo"}, }, ), datetime.timedelta(days=-1, seconds=50400): {"Pacific/Apia"}, }, ), datetime.timedelta(days=-1, seconds=50400): ( datetime.datetime(1983, 10, 30, 21, 0), { datetime.timedelta(days=-1, seconds=50400): { "America/Adak", "America/Atka", "US/Aleutian", }, datetime.timedelta(days=-1, seconds=54000): {"America/Nome"}, }, ), }, ), datetime.timedelta(days=-1, seconds=48000): {"Pacific/Kiritimati"}, datetime.timedelta(days=-1, seconds=48600): {"Pacific/Rarotonga"}, datetime.timedelta(days=-1, seconds=50400): ( datetime.datetime(1970, 4, 26, 15, 0), { datetime.timedelta(days=-1, seconds=50400): { "Etc/GMT+10", "HST", "Pacific/Honolulu", "Pacific/Johnston", "Pacific/Tahiti", "US/Hawaii", }, datetime.timedelta(days=-1, seconds=54000): { "America/Anchorage", "US/Alaska", }, }, ), datetime.timedelta(days=-1, seconds=52200): {"Pacific/Marquesas"}, datetime.timedelta(days=-1, seconds=54000): ( datetime.datetime(1970, 4, 26, 15, 0), { datetime.timedelta(days=-1, seconds=54000): ( datetime.datetime(1973, 10, 28, 18, 0), { datetime.timedelta(days=-1, seconds=54000): { "Etc/GMT+9", "Pacific/Gambier", }, datetime.timedelta(days=-1, seconds=57600): {"America/Dawson"}, }, ), datetime.timedelta(days=-1, seconds=57600): {"America/Yakutat"}, }, ), datetime.timedelta(days=-1, seconds=55800): {"Pacific/Pitcairn"}, datetime.timedelta(days=-1, seconds=57600): ( datetime.datetime(1970, 4, 26, 15, 0), { datetime.timedelta(days=-1, seconds=57600): ( datetime.datetime(1972, 4, 30, 21, 0), { datetime.timedelta(days=-1, seconds=57600): ( datetime.datetime(1976, 4, 25, 15, 0), { datetime.timedelta(days=-1, seconds=57600): ( datetime.datetime(1980, 4, 27, 15, 0), { datetime.timedelta(days=-1, seconds=57600): { "Etc/GMT+8" }, datetime.timedelta(days=-1, seconds=61200): { "America/Whitehorse", "Canada/Yukon", }, }, ), datetime.timedelta(days=-1, seconds=61200): { "America/Ensenada", "America/Santa_Isabel", "America/Tijuana", "Mexico/BajaNorte", }, }, ), datetime.timedelta(days=-1, seconds=61200): {"America/Inuvik"}, }, ), datetime.timedelta(days=-1, seconds=61200): ( datetime.datetime(1972, 10, 30, 0, 0), { datetime.timedelta(days=-1, seconds=57600): ( datetime.datetime(1974, 1, 6, 18, 0), { datetime.timedelta(days=-1, seconds=57600): ( datetime.datetime(2015, 11, 1, 21, 0), { datetime.timedelta(days=-1, seconds=57600): { "America/Vancouver", "Canada/Pacific", }, datetime.timedelta(days=-1, seconds=61200): { "America/Fort_Nelson" }, }, ), datetime.timedelta(days=-1, seconds=61200): ( datetime.datetime(1980, 4, 28, 0, 0), { datetime.timedelta(days=-1, seconds=57600): { "America/Juneau" }, datetime.timedelta(days=-1, seconds=61200): ( datetime.datetime(1983, 10, 30, 21, 0), { datetime.timedelta( days=-1, seconds=54000 ): {"America/Sitka"}, datetime.timedelta( days=-1, seconds=57600 ): ( datetime.datetime( 1984, 4, 29, 21, 0 ), { datetime.timedelta( days=-1, seconds=57600 ): {"America/Metlakatla"}, datetime.timedelta( days=-1, seconds=61200 ): { "America/Los_Angeles", "PST8PDT", "US/Pacific", }, }, ), }, ), }, ), }, ), datetime.timedelta(days=-1, seconds=61200): { "America/Dawson_Creek" }, }, ), }, ), datetime.timedelta(days=-1, seconds=61200): ( datetime.datetime(1970, 4, 26, 15, 0), { datetime.timedelta(days=-1, seconds=61200): ( datetime.datetime(1972, 4, 30, 21, 0), { datetime.timedelta(days=-1, seconds=61200): ( datetime.datetime(1996, 4, 7, 15, 0), { datetime.timedelta(days=-1, seconds=61200): { "America/Creston", "America/Phoenix", "Etc/GMT+7", "MST", "US/Arizona", }, datetime.timedelta(days=-1, seconds=64800): ( datetime.datetime(1999, 4, 4, 15, 0), { datetime.timedelta(days=-1, seconds=61200): { "America/Hermosillo" }, datetime.timedelta(days=-1, seconds=64800): ( datetime.datetime(2010, 4, 5, 0, 0), { datetime.timedelta( days=-1, seconds=64800 ): { "America/Mazatlan", "Mexico/BajaSur", }, datetime.timedelta( days=-1, seconds=68400 ): {"America/Bahia_Banderas"}, }, ), }, ), }, ), datetime.timedelta(days=-1, seconds=64800): ( datetime.datetime(1972, 10, 29, 21, 0), { datetime.timedelta(days=-1, seconds=61200): ( datetime.datetime(1999, 11, 1, 0, 0), { datetime.timedelta(days=-1, seconds=61200): { "America/Edmonton", "America/Yellowknife", "Canada/Mountain", }, datetime.timedelta(days=-1, seconds=64800): { "America/Cambridge_Bay" }, }, ), datetime.timedelta(days=-1, seconds=64800): { "America/Swift_Current" }, }, ), }, ), datetime.timedelta(days=-1, seconds=64800): ( datetime.datetime(1974, 1, 6, 18, 0), { datetime.timedelta(days=-1, seconds=61200): {"America/Boise"}, datetime.timedelta(days=-1, seconds=64800): ( datetime.datetime(1992, 10, 25, 18, 0), { datetime.timedelta(days=-1, seconds=61200): ( datetime.datetime(2003, 10, 26, 15, 0), { datetime.timedelta(days=-1, seconds=61200): ( datetime.datetime(2010, 11, 7, 21, 0), { datetime.timedelta( days=-1, seconds=61200 ): { "America/Denver", "America/Shiprock", "MST7MDT", "Navajo", "US/Mountain", }, datetime.timedelta( days=-1, seconds=64800 ): {"America/North_Dakota/Beulah"}, }, ), datetime.timedelta(days=-1, seconds=64800): { "America/North_Dakota/New_Salem" }, }, ), datetime.timedelta(days=-1, seconds=64800): { "America/North_Dakota/Center" }, }, ), }, ), }, ), datetime.timedelta(days=-1, seconds=64800): ( datetime.datetime(1970, 3, 29, 15, 0), { datetime.timedelta(days=-1, seconds=61200): { "Chile/EasterIsland", "Pacific/Easter", }, datetime.timedelta(days=-1, seconds=64800): ( datetime.datetime(1970, 4, 26, 15, 0), { datetime.timedelta(days=-1, seconds=64800): ( datetime.datetime(1972, 4, 30, 21, 0), { datetime.timedelta(days=-1, seconds=64800): ( datetime.datetime(1973, 5, 1, 15, 0), { datetime.timedelta(days=-1, seconds=64800): ( datetime.datetime(1973, 11, 25, 18, 0), { datetime.timedelta( days=-1, seconds=64800 ): ( datetime.datetime( 1973, 12, 5, 21, 0 ), { datetime.timedelta( days=-1, seconds=64800 ): ( datetime.datetime( 1979, 2, 26, 0, 0 ), { datetime.timedelta( days=-1, seconds=64800, ): ( datetime.datetime( 1981, 12, 27, 0, 0, ), { datetime.timedelta( days=-1, seconds=64800, ): ( datetime.datetime( 1987, 5, 4, 0, 0, ), { datetime.timedelta( days=-1, seconds=64800, ): ( datetime.datetime( 1988, 4, 4, 0, 0, ), { datetime.timedelta( days=-1, seconds=64800, ): ( datetime.datetime( 1996, 4, 7, 15, 0, ), { datetime.timedelta( days=-1, seconds=64800, ): { "America/Regina", "Canada/Saskatchewan", "Etc/GMT+6", }, datetime.timedelta( days=-1, seconds=68400, ): ( datetime.datetime( 1998, 4, 5, 15, 0, ), { datetime.timedelta( days=-1, seconds=64800, ): ( datetime.datetime( 2010, 3, 14, 21, 0, ), { datetime.timedelta( days=-1, seconds=61200, ): { "America/Chihuahua" }, datetime.timedelta( days=-1, seconds=64800, ): { "America/Ciudad_Juarez", "America/Ojinaga", }, }, ), datetime.timedelta( days=-1, seconds=68400, ): { "America/Mexico_City", "Mexico/General", }, }, ), }, ), datetime.timedelta( days=-1, seconds=68400, ): ( datetime.datetime( 2010, 3, 14, 21, 0, ), { datetime.timedelta( days=-1, seconds=64800, ): { "America/Monterrey" }, datetime.timedelta( days=-1, seconds=68400, ): { "America/Matamoros" }, }, ), }, ), datetime.timedelta( days=-1, seconds=68400, ): ( datetime.datetime( 2006, 5, 8, 0, 0, ), { datetime.timedelta( days=-1, seconds=64800, ): { "America/El_Salvador" }, datetime.timedelta( days=-1, seconds=68400, ): { "America/Tegucigalpa" }, }, ), }, ), datetime.timedelta( days=-1, seconds=68400, ): ( datetime.datetime( 1982, 11, 2, 18, 0, ), { datetime.timedelta( days=-1, seconds=64800, ): { "America/Merida" }, datetime.timedelta( days=-1, seconds=68400, ): { "America/Cancun" }, }, ), }, ), datetime.timedelta( days=-1, seconds=68400, ): { "America/Costa_Rica" }, }, ), datetime.timedelta( days=-1, seconds=68400 ): {"America/Belize"}, }, ), datetime.timedelta( days=-1, seconds=68400 ): {"America/Guatemala"}, }, ), datetime.timedelta(days=-1, seconds=68400): { "America/Managua" }, }, ), datetime.timedelta(days=-1, seconds=68400): ( datetime.datetime(2006, 10, 29, 21, 0), { datetime.timedelta(days=-1, seconds=64800): { "America/Rankin_Inlet" }, datetime.timedelta(days=-1, seconds=68400): { "America/Resolute" }, }, ), }, ), datetime.timedelta(days=-1, seconds=68400): ( datetime.datetime(1974, 1, 6, 18, 0), { datetime.timedelta(days=-1, seconds=64800): { "America/Rainy_River", "America/Winnipeg", "Canada/Central", }, datetime.timedelta(days=-1, seconds=68400): ( datetime.datetime(1977, 10, 31, 0, 0), { datetime.timedelta(days=-1, seconds=64800): ( datetime.datetime(1991, 10, 27, 18, 0), { datetime.timedelta( days=-1, seconds=64800 ): ( datetime.datetime( 2000, 10, 29, 15, 0 ), { datetime.timedelta( days=-1, seconds=64800 ): { "America/Chicago", "CST6CDT", "US/Central", }, datetime.timedelta( days=-1, seconds=68400 ): { "America/Kentucky/Monticello" }, }, ), datetime.timedelta( days=-1, seconds=68400 ): { "America/Indiana/Knox", "America/Knox_IN", "US/Indiana-Starke", }, }, ), datetime.timedelta(days=-1, seconds=68400): { "America/Indiana/Petersburg" }, }, ), }, ), }, ), }, ), datetime.timedelta(days=-1, seconds=68400): ( datetime.datetime(1970, 4, 26, 15, 0), { datetime.timedelta(days=-1, seconds=68400): ( datetime.datetime(1972, 4, 30, 21, 0), { datetime.timedelta(days=-1, seconds=68400): ( datetime.datetime(1973, 4, 29, 21, 0), { datetime.timedelta(days=-1, seconds=68400): ( datetime.datetime(1973, 10, 28, 21, 0), { datetime.timedelta(days=-1, seconds=64800): { "America/Menominee" }, datetime.timedelta(days=-1, seconds=68400): ( datetime.datetime(1974, 1, 6, 15, 0), { datetime.timedelta( days=-1, seconds=68400 ): ( datetime.datetime( 1979, 4, 29, 21, 0 ), { datetime.timedelta( days=-1, seconds=68400 ): ( datetime.datetime( 1983, 5, 8, 18, 0 ), { datetime.timedelta( days=-1, seconds=68400, ): ( datetime.datetime( 1985, 11, 2, 15, 0, ), { datetime.timedelta( days=-1, seconds=68400, ): ( datetime.datetime( 1986, 1, 1, 18, 0, ), { datetime.timedelta( days=-1, seconds=64800, ): { "Pacific/Galapagos" }, datetime.timedelta( days=-1, seconds=68400, ): ( datetime.datetime( 1992, 5, 4, 0, 0, ), { datetime.timedelta( days=-1, seconds=68400, ): ( datetime.datetime( 1992, 11, 28, 15, 0, ), { datetime.timedelta( days=-1, seconds=68400, ): { "America/Atikokan", "America/Cayman", "America/Coral_Harbour", "America/Panama", "EST", "Etc/GMT+5", }, datetime.timedelta( days=-1, seconds=72000, ): { "America/Guayaquil" }, }, ), datetime.timedelta( days=-1, seconds=72000, ): { "America/Bogota" }, }, ), datetime.timedelta( days=-1, seconds=72000, ): { "America/Lima" }, }, ), datetime.timedelta( days=-1, seconds=72000, ): ( datetime.datetime( 1993, 10, 17, 15, 0, ), { datetime.timedelta( days=-1, seconds=68400, ): { "America/Porto_Acre", "America/Rio_Branco", "Brazil/Acre", }, datetime.timedelta( days=-1, seconds=72000, ): { "America/Eirunepe" }, }, ), }, ), datetime.timedelta( days=-1, seconds=72000, ): { "America/Port-au-Prince" }, }, ), datetime.timedelta( days=-1, seconds=72000 ): {"America/Grand_Turk"}, }, ), datetime.timedelta( days=-1, seconds=72000 ): {"America/Jamaica", "Jamaica"}, }, ), }, ), datetime.timedelta(days=-1, seconds=72000): { "America/Detroit", "US/Michigan", }, }, ), datetime.timedelta(days=-1, seconds=72000): { "America/Iqaluit", "America/Pangnirtung", }, }, ), datetime.timedelta(days=-1, seconds=72000): ( datetime.datetime(1971, 4, 25, 15, 0), { datetime.timedelta(days=-1, seconds=68400): ( datetime.datetime(2006, 4, 2, 15, 0), { datetime.timedelta(days=-1, seconds=68400): ( datetime.datetime(2007, 3, 12, 3, 0), { datetime.timedelta(days=-1, seconds=68400): ( datetime.datetime(2007, 11, 4, 15, 0), { datetime.timedelta( days=-1, seconds=64800 ): {"America/Indiana/Tell_City"}, datetime.timedelta( days=-1, seconds=68400 ): {"America/Indiana/Vincennes"}, }, ), datetime.timedelta(days=-1, seconds=72000): { "America/Indiana/Winamac" }, }, ), datetime.timedelta(days=-1, seconds=72000): { "America/Fort_Wayne", "America/Indiana/Indianapolis", "America/Indianapolis", "US/East-Indiana", }, }, ), datetime.timedelta(days=-1, seconds=72000): ( datetime.datetime(1972, 10, 8, 12, 0), { datetime.timedelta(days=-1, seconds=68400): { "America/Havana", "Cuba", }, datetime.timedelta(days=-1, seconds=72000): ( datetime.datetime(1973, 4, 29, 15, 0), { datetime.timedelta(days=-1, seconds=68400): { "America/Indiana/Vevay" }, datetime.timedelta(days=-1, seconds=72000): ( datetime.datetime(1974, 1, 6, 18, 0), { datetime.timedelta( days=-1, seconds=68400 ): ( datetime.datetime( 1974, 4, 28, 15, 0 ), { datetime.timedelta( days=-1, seconds=68400 ): ( datetime.datetime( 1976, 4, 26, 0, 0 ), { datetime.timedelta( days=-1, seconds=68400, ): { "America/Indiana/Marengo" }, datetime.timedelta( days=-1, seconds=72000, ): { "America/Kentucky/Louisville", "America/Louisville", }, }, ), datetime.timedelta( days=-1, seconds=72000 ): { "America/Montreal", "America/Nassau", "America/Nipigon", "America/Thunder_Bay", "America/Toronto", "Canada/Eastern", }, }, ), datetime.timedelta( days=-1, seconds=72000 ): { "America/New_York", "EST5EDT", "US/Eastern", }, }, ), }, ), }, ), }, ), }, ), datetime.timedelta(days=-1, seconds=70200): {"America/Santo_Domingo"}, datetime.timedelta(days=-1, seconds=72000): ( datetime.datetime(1970, 4, 26, 15, 0), { datetime.timedelta(days=-1, seconds=72000): ( datetime.datetime(1972, 4, 30, 21, 0), { datetime.timedelta(days=-1, seconds=72000): ( datetime.datetime(1972, 10, 2, 0, 0), { datetime.timedelta(days=-1, seconds=72000): ( datetime.datetime(1974, 4, 28, 21, 0), { datetime.timedelta(days=-1, seconds=72000): ( datetime.datetime(1977, 6, 12, 18, 0), { datetime.timedelta( days=-1, seconds=72000 ): ( datetime.datetime( 1980, 4, 6, 18, 0 ), { datetime.timedelta( days=-1, seconds=72000 ): ( datetime.datetime( 1980, 5, 2, 0, 0 ), { datetime.timedelta( days=-1, seconds=72000, ): ( datetime.datetime( 1983, 5, 2, 0, 0 ), { datetime.timedelta( days=-1, seconds=72000, ): ( datetime.datetime( 1985, 11, 2, 18, 0, ), { datetime.timedelta( days=-1, seconds=72000, ): ( datetime.datetime( 1991, 3, 31, 18, 0, ), { datetime.timedelta( days=-1, seconds=72000, ): ( datetime.datetime( 2007, 12, 10, 0, 0, ), { datetime.timedelta( days=-1, seconds=70200, ): { "America/Caracas" }, datetime.timedelta( days=-1, seconds=72000, ): { "America/Anguilla", "America/Antigua", "America/Aruba", "America/Blanc-Sablon", "America/Curacao", "America/Dominica", "America/Grenada", "America/Guadeloupe", "America/Kralendijk", "America/La_Paz", "America/Lower_Princes", "America/Marigot", "America/Montserrat", "America/Port_of_Spain", "America/Puerto_Rico", "America/St_Barthelemy", "America/St_Kitts", "America/St_Lucia", "America/St_Thomas", "America/St_Vincent", "America/Tortola", "America/Virgin", "Etc/GMT+4", }, }, ), datetime.timedelta( days=-1, seconds=75600, ): { "America/Thule" }, }, ), datetime.timedelta( days=-1, seconds=75600, ): ( datetime.datetime( 1988, 10, 16, 15, 0, ), { datetime.timedelta( days=-1, seconds=72000, ): ( datetime.datetime( 1993, 10, 17, 15, 0, ), { datetime.timedelta( days=-1, seconds=72000, ): ( datetime.datetime( 1999, 10, 3, 18, 0, ), { datetime.timedelta( days=-1, seconds=72000, ): ( datetime.datetime( 2008, 6, 24, 15, 0, ), { datetime.timedelta( days=-1, seconds=72000, ): { "America/Porto_Velho" }, datetime.timedelta( days=-1, seconds=75600, ): { "America/Santarem" }, }, ), datetime.timedelta( days=-1, seconds=75600, ): { "America/Boa_Vista" }, }, ), datetime.timedelta( days=-1, seconds=75600, ): { "America/Manaus", "Brazil/West", }, }, ), datetime.timedelta( days=-1, seconds=75600, ): ( datetime.datetime( 2003, 10, 19, 21, 0, ), { datetime.timedelta( days=-1, seconds=72000, ): { "America/Cuiaba" }, datetime.timedelta( days=-1, seconds=75600, ): { "America/Campo_Grande" }, }, ), }, ), }, ), datetime.timedelta( days=-1, seconds=75600, ): { "Atlantic/Stanley" }, }, ), datetime.timedelta( days=-1, seconds=75600, ): {"America/Miquelon"}, }, ), datetime.timedelta( days=-1, seconds=75600 ): {"America/Martinique"}, }, ), datetime.timedelta( days=-1, seconds=75600 ): {"America/Barbados"}, }, ), datetime.timedelta(days=-1, seconds=75600): { "Atlantic/Bermuda" }, }, ), datetime.timedelta(days=-1, seconds=75600): { "America/Asuncion" }, }, ), datetime.timedelta(days=-1, seconds=75600): { "America/Glace_Bay" }, }, ), datetime.timedelta(days=-1, seconds=75600): ( datetime.datetime(1973, 4, 30, 0, 0), { datetime.timedelta(days=-1, seconds=72000): {"America/Moncton"}, datetime.timedelta(days=-1, seconds=75600): ( datetime.datetime(1988, 4, 3, 15, 0), { datetime.timedelta(days=-1, seconds=75600): { "America/Halifax", "Canada/Atlantic", }, datetime.timedelta(days=-1, seconds=79200): { "America/Goose_Bay" }, }, ), }, ), }, ), datetime.timedelta(days=-1, seconds=72900): {"America/Guyana"}, datetime.timedelta(days=-1, seconds=73800): ( datetime.datetime(1970, 4, 26, 15, 0), { datetime.timedelta(days=-1, seconds=73800): {"America/Paramaribo"}, datetime.timedelta(days=-1, seconds=77400): { "America/St_Johns", "Canada/Newfoundland", }, }, ), datetime.timedelta(days=-1, seconds=75600): ( datetime.datetime(1970, 3, 29, 15, 0), { datetime.timedelta(days=-1, seconds=72000): ( datetime.datetime(2017, 5, 14, 18, 0), { datetime.timedelta(days=-1, seconds=72000): { "America/Santiago", "Chile/Continental", }, datetime.timedelta(days=-1, seconds=75600): { "America/Punta_Arenas" }, }, ), datetime.timedelta(days=-1, seconds=75600): ( datetime.datetime(1970, 4, 25, 18, 0), { datetime.timedelta(days=-1, seconds=75600): ( datetime.datetime(1974, 1, 23, 21, 0), { datetime.timedelta(days=-1, seconds=75600): ( datetime.datetime(1980, 4, 7, 0, 0), { datetime.timedelta(days=-1, seconds=75600): ( datetime.datetime(1985, 11, 3, 0, 0), { datetime.timedelta( days=-1, seconds=75600 ): {"America/Cayenne", "Etc/GMT+3"}, datetime.timedelta( days=-1, seconds=79200 ): ( datetime.datetime( 1988, 10, 16, 15, 0 ), { datetime.timedelta( days=-1, seconds=75600 ): {"America/Belem"}, datetime.timedelta( days=-1, seconds=79200 ): ( datetime.datetime( 1990, 10, 22, 0, 0 ), { datetime.timedelta( days=-1, seconds=75600, ): ( datetime.datetime( 1995, 10, 15, 15, 0, ), { datetime.timedelta( days=-1, seconds=75600, ): ( datetime.datetime( 2000, 10, 15, 12, 0, ), { datetime.timedelta( days=-1, seconds=75600, ): { "America/Recife" }, datetime.timedelta( days=-1, seconds=79200, ): { "America/Fortaleza" }, }, ), datetime.timedelta( days=-1, seconds=79200, ): ( datetime.datetime( 1996, 10, 6, 18, 0, ), { datetime.timedelta( days=-1, seconds=75600, ): { "America/Maceio" }, datetime.timedelta( days=-1, seconds=79200, ): { "America/Araguaina" }, }, ), }, ), datetime.timedelta( days=-1, seconds=79200, ): ( datetime.datetime( 2003, 10, 19, 21, 0, ), { datetime.timedelta( days=-1, seconds=75600, ): { "America/Bahia" }, datetime.timedelta( days=-1, seconds=79200, ): { "America/Sao_Paulo", "Brazil/East", }, }, ), }, ), }, ), }, ), datetime.timedelta(days=-1, seconds=79200): ( datetime.datetime(1996, 1, 2, 0, 0), { datetime.timedelta( days=-1, seconds=75600 ): {"America/Godthab", "America/Nuuk"}, datetime.timedelta(0): { "America/Danmarkshavn" }, }, ), }, ), datetime.timedelta(days=-1, seconds=79200): ( datetime.datetime(1982, 5, 1, 21, 0), { datetime.timedelta(days=-1, seconds=72000): { "Antarctica/Palmer" }, datetime.timedelta(days=-1, seconds=75600): ( datetime.datetime(1990, 3, 4, 21, 0), { datetime.timedelta( days=-1, seconds=72000 ): ( datetime.datetime( 1990, 10, 16, 0, 0 ), { datetime.timedelta( days=-1, seconds=72000 ): { "America/Argentina/Jujuy", "America/Jujuy", }, datetime.timedelta( days=-1, seconds=75600 ): { "America/Argentina/Mendoza", "America/Mendoza", }, }, ), datetime.timedelta( days=-1, seconds=75600 ): ( datetime.datetime( 1991, 3, 1, 15, 0 ), { datetime.timedelta( days=-1, seconds=72000 ): ( datetime.datetime( 2004, 6, 20, 18, 0 ), { datetime.timedelta( days=-1, seconds=72000, ): { "America/Argentina/San_Juan" }, datetime.timedelta( days=-1, seconds=75600, ): { "America/Argentina/La_Rioja" }, }, ), datetime.timedelta( days=-1, seconds=79200 ): ( datetime.datetime( 1991, 3, 4, 0, 0 ), { datetime.timedelta( days=-1, seconds=72000, ): ( datetime.datetime( 2004, 6, 1, 12, 0, ), { datetime.timedelta( days=-1, seconds=72000, ): ( datetime.datetime( 2004, 6, 14, 0, 0, ), { datetime.timedelta( days=-1, seconds=72000, ): { "America/Argentina/Catamarca", "America/Argentina/ComodRivadavia", "America/Catamarca", }, datetime.timedelta( days=-1, seconds=75600, ): { "America/Argentina/Tucuman" }, }, ), datetime.timedelta( days=-1, seconds=75600, ): ( datetime.datetime( 2008, 10, 19, 15, 0, ), { datetime.timedelta( days=-1, seconds=75600, ): { "America/Argentina/Salta" }, datetime.timedelta( days=-1, seconds=79200, ): { "America/Argentina/Cordoba", "America/Cordoba", "America/Rosario", }, }, ), }, ), datetime.timedelta( days=-1, seconds=75600, ): ( datetime.datetime( 2004, 5, 30, 15, 0, ), { datetime.timedelta( days=-1, seconds=72000, ): { "America/Argentina/Ushuaia" }, datetime.timedelta( days=-1, seconds=75600, ): ( datetime.datetime( 2004, 6, 1, 12, 0, ), { datetime.timedelta( days=-1, seconds=72000, ): { "America/Argentina/Rio_Gallegos" }, datetime.timedelta( days=-1, seconds=75600, ): { "America/Argentina/Buenos_Aires", "America/Buenos_Aires", }, }, ), }, ), }, ), }, ), datetime.timedelta( days=-1, seconds=79200 ): {"America/Argentina/San_Luis"}, }, ), }, ), }, ), datetime.timedelta(days=-1, seconds=79200): { "America/Montevideo" }, }, ), }, ), datetime.timedelta(days=-1, seconds=79200): ( datetime.datetime(1975, 11, 25, 18, 0), { datetime.timedelta(days=-1, seconds=79200): ( datetime.datetime(1980, 4, 6, 21, 0), { datetime.timedelta(days=-1, seconds=79200): ( datetime.datetime(1985, 11, 2, 21, 0), { datetime.timedelta(days=-1, seconds=79200): { "Atlantic/South_Georgia", "Etc/GMT+2", }, datetime.timedelta(days=-1, seconds=82800): { "America/Noronha", "Brazil/DeNoronha", }, }, ), datetime.timedelta(days=-1, seconds=82800): { "America/Scoresbysund" }, }, ), datetime.timedelta(days=-1, seconds=82800): {"Atlantic/Cape_Verde"}, }, ), datetime.timedelta(days=-1, seconds=82800): ( datetime.datetime(1975, 1, 1, 15, 0), { datetime.timedelta(days=-1, seconds=82800): ( datetime.datetime(1976, 4, 15, 0, 0), { datetime.timedelta(days=-1, seconds=82800): ( datetime.datetime(1982, 3, 29, 0, 0), { datetime.timedelta(days=-1, seconds=82800): { "Etc/GMT+1" }, datetime.timedelta(0): {"Atlantic/Azores"}, }, ), datetime.timedelta(0): {"Africa/El_Aaiun"}, }, ), datetime.timedelta(0): {"Africa/Bissau"}, }, ), datetime.timedelta(days=-1, seconds=83730): {"Africa/Monrovia"}, datetime.timedelta(days=-1, seconds=83760): {"Africa/Monrovia"}, datetime.timedelta(0): ( datetime.datetime(1971, 4, 26, 12, 0), { datetime.timedelta(0): ( datetime.datetime(1974, 6, 25, 0, 0), { datetime.timedelta(0): ( datetime.datetime(1976, 12, 1, 15, 0), { datetime.timedelta(days=-1, seconds=75600): { "Antarctica/Rothera" }, datetime.timedelta(0): ( datetime.datetime(1977, 4, 4, 0, 0), { datetime.timedelta(0): ( datetime.datetime(1980, 4, 6, 15, 0), { datetime.timedelta(0): ( datetime.datetime( 1981, 3, 29, 18, 0 ), { datetime.timedelta(0): ( datetime.datetime( 1982, 4, 4, 15, 0 ), { datetime.timedelta(0): ( datetime.datetime( 2005, 3, 27, 15, 0, ), { datetime.timedelta( 0 ): ( datetime.datetime( 2018, 1, 1, 18, 0, ), { datetime.timedelta( 0 ): { "Africa/Abidjan", "Africa/Accra", "Africa/Bamako", "Africa/Banjul", "Africa/Conakry", "Africa/Dakar", "Africa/Freetown", "Africa/Lome", "Africa/Nouakchott", "Africa/Ouagadougou", "Africa/Timbuktu", "Atlantic/Reykjavik", "Atlantic/St_Helena", "Etc/GMT", "Etc/GMT+0", "Etc/GMT-0", "Etc/GMT0", "Etc/Greenwich", "Etc/UCT", "Etc/UTC", "Etc/Universal", "Etc/Zulu", "GMT", "GMT+0", "GMT-0", "GMT0", "Greenwich", "Iceland", "UCT", "UTC", "Universal", "Zulu", }, datetime.timedelta( seconds=3600 ): { "Africa/Sao_Tome" }, }, ), datetime.timedelta( seconds=7200 ): { "Antarctica/Troll" }, }, ), datetime.timedelta( seconds=3600 ): {"Atlantic/Madeira"}, }, ), datetime.timedelta( seconds=3600 ): { "Atlantic/Faeroe", "Atlantic/Faroe", }, }, ), datetime.timedelta(seconds=3600): { "Atlantic/Canary" }, }, ), datetime.timedelta(seconds=3600): {"WET"}, }, ), }, ), datetime.timedelta(seconds=3600): ( datetime.datetime(1986, 1, 1, 12, 0), { datetime.timedelta(0): {"Africa/Casablanca"}, datetime.timedelta(seconds=3600): {"Africa/Ceuta"}, }, ), }, ), datetime.timedelta(seconds=3600): {"Africa/Algiers"}, }, ), datetime.timedelta(seconds=3600): ( datetime.datetime(1970, 6, 1, 0, 0), { datetime.timedelta(seconds=3600): ( datetime.datetime(1971, 11, 1, 0, 0), { datetime.timedelta(0): { "Eire", "Europe/Belfast", "Europe/Dublin", "Europe/Guernsey", "Europe/Isle_of_Man", "Europe/Jersey", "Europe/London", "GB", "GB-Eire", }, datetime.timedelta(seconds=3600): ( datetime.datetime(1974, 4, 14, 15, 0), { datetime.timedelta(seconds=3600): ( datetime.datetime(1974, 5, 4, 21, 0), { datetime.timedelta(seconds=3600): ( datetime.datetime(1976, 3, 28, 21, 0), { datetime.timedelta(seconds=3600): ( datetime.datetime( 1976, 9, 26, 21, 0 ), { datetime.timedelta(0): ( datetime.datetime( 1980, 3, 30, 18, 0 ), { datetime.timedelta(0): { "Europe/Lisbon", "Portugal", "WET", }, datetime.timedelta( seconds=3600 ): {"Portugal"}, }, ), datetime.timedelta( seconds=3600 ): ( datetime.datetime( 1977, 4, 3, 18, 0 ), { datetime.timedelta( seconds=3600 ): ( datetime.datetime( 1977, 4, 30, 21, 0, ), { datetime.timedelta( seconds=3600 ): ( datetime.datetime( 1979, 4, 1, 18, 0, ), { datetime.timedelta( seconds=3600 ): ( datetime.datetime( 1979, 10, 15, 0, 0, ), { datetime.timedelta( seconds=3600 ): ( datetime.datetime( 1980, 4, 6, 15, 0, ), { datetime.timedelta( seconds=3600 ): ( datetime.datetime( 1981, 3, 29, 18, 0, ), { datetime.timedelta( seconds=3600 ): ( datetime.datetime( 1982, 3, 28, 18, 0, ), { datetime.timedelta( seconds=3600 ): ( datetime.datetime( 1983, 3, 27, 18, 0, ), { datetime.timedelta( seconds=3600 ): ( datetime.datetime( 1985, 4, 1, 0, 0, ), { datetime.timedelta( seconds=3600 ): { "Africa/Bangui", "Africa/Brazzaville", "Africa/Douala", "Africa/Kinshasa", "Africa/Lagos", "Africa/Libreville", "Africa/Luanda", "Africa/Malabo", "Africa/Niamey", "Africa/Porto-Novo", "Etc/GMT-1", }, datetime.timedelta( seconds=7200 ): { "Europe/Andorra" }, }, ), datetime.timedelta( seconds=7200 ): { "Europe/Belgrade", "Europe/Ljubljana", "Europe/Podgorica", "Europe/Sarajevo", "Europe/Skopje", "Europe/Zagreb", }, }, ), datetime.timedelta( seconds=7200 ): { "Europe/Gibraltar" }, }, ), datetime.timedelta( seconds=7200 ): { "Europe/Busingen", "Europe/Vaduz", "Europe/Zurich", }, }, ), datetime.timedelta( seconds=7200 ): { "Arctic/Longyearbyen", "Atlantic/Jan_Mayen", "Europe/Berlin", "Europe/Budapest", "Europe/Copenhagen", "Europe/Oslo", "Europe/Stockholm", "Europe/Vienna", }, }, ), datetime.timedelta( seconds=7200 ): { "Africa/Ndjamena" }, }, ), datetime.timedelta( seconds=7200 ): { "Europe/Bratislava", "Europe/Prague", }, }, ), datetime.timedelta( seconds=7200 ): { "Africa/Tunis" }, }, ), datetime.timedelta( seconds=7200 ): { "CET", "Europe/Amsterdam", "Europe/Brussels", "Europe/Luxembourg", "Europe/Warsaw", "MET", "Poland", }, }, ), }, ), datetime.timedelta(seconds=7200): { "Europe/Monaco", "Europe/Paris", }, }, ), datetime.timedelta(seconds=7200): { "Europe/Tirane" }, }, ), datetime.timedelta(seconds=7200): {"Europe/Madrid"}, }, ), }, ), datetime.timedelta(seconds=7200): ( datetime.datetime(1973, 3, 31, 18, 0), { datetime.timedelta(seconds=3600): { "Europe/Rome", "Europe/San_Marino", "Europe/Vatican", }, datetime.timedelta(seconds=7200): {"Europe/Malta"}, }, ), }, ), datetime.timedelta(seconds=7200): ( datetime.datetime(1970, 5, 1, 15, 0), { datetime.timedelta(seconds=7200): ( datetime.datetime(1972, 6, 23, 0, 0), { datetime.timedelta(seconds=7200): ( datetime.datetime(1973, 6, 3, 15, 0), { datetime.timedelta(seconds=7200): ( datetime.datetime(1973, 6, 6, 21, 0), { datetime.timedelta(seconds=7200): ( datetime.datetime(1974, 7, 8, 0, 0), { datetime.timedelta(seconds=7200): ( datetime.datetime( 1975, 4, 13, 18, 0 ), { datetime.timedelta( seconds=7200 ): ( datetime.datetime( 1977, 4, 3, 18, 0 ), { datetime.timedelta( seconds=7200 ): ( datetime.datetime( 1979, 4, 1, 15, 0, ), { datetime.timedelta( seconds=7200 ): ( datetime.datetime( 1979, 5, 27, 15, 0, ), { datetime.timedelta( seconds=7200 ): ( datetime.datetime( 1981, 3, 29, 15, 0, ), { datetime.timedelta( seconds=7200 ): ( datetime.datetime( 1982, 1, 1, 21, 0, ), { datetime.timedelta( seconds=3600 ): { "Africa/Tripoli", "Libya", }, datetime.timedelta( seconds=7200 ): ( datetime.datetime( 1994, 3, 21, 18, 0, ), { datetime.timedelta( seconds=3600 ): { "Africa/Windhoek" }, datetime.timedelta( seconds=7200 ): { "Africa/Blantyre", "Africa/Bujumbura", "Africa/Gaborone", "Africa/Harare", "Africa/Johannesburg", "Africa/Kigali", "Africa/Lubumbashi", "Africa/Lusaka", "Africa/Maputo", "Africa/Maseru", "Africa/Mbabane", "Etc/GMT-2", }, }, ), }, ), datetime.timedelta( seconds=10800 ): { "Europe/Helsinki", "Europe/Mariehamn", }, }, ), datetime.timedelta( seconds=10800 ): { "Europe/Bucharest" }, }, ), datetime.timedelta( seconds=10800 ): { "Europe/Sofia" }, }, ), datetime.timedelta( seconds=10800 ): {"EET"}, }, ), datetime.timedelta( seconds=10800 ): ( datetime.datetime( 1975, 10, 12, 18, 0 ), { datetime.timedelta( seconds=7200 ): ( datetime.datetime( 2016, 10, 31, 3, 0, ), { datetime.timedelta( seconds=7200 ): { "Asia/Nicosia", "Europe/Nicosia", }, datetime.timedelta( seconds=10800 ): { "Asia/Famagusta" }, }, ), datetime.timedelta( seconds=10800 ): { "EET", "Europe/Athens", }, }, ), }, ), datetime.timedelta(seconds=10800): ( datetime.datetime( 1996, 3, 15, 21, 0 ), { datetime.timedelta( seconds=7200 ): ( datetime.datetime( 2008, 8, 29, 18, 0 ), { datetime.timedelta( seconds=7200 ): {"Asia/Gaza"}, datetime.timedelta( seconds=10800 ): {"Asia/Hebron"}, }, ), datetime.timedelta( seconds=10800 ): { "Asia/Jerusalem", "Asia/Tel_Aviv", "Israel", }, }, ), }, ), datetime.timedelta(seconds=10800): { "Asia/Amman" }, }, ), datetime.timedelta(seconds=10800): { "Asia/Istanbul", "Europe/Istanbul", "Turkey", }, }, ), datetime.timedelta(seconds=10800): {"Asia/Beirut"}, }, ), datetime.timedelta(seconds=10800): ( datetime.datetime(1970, 10, 1, 18, 0), { datetime.timedelta(seconds=7200): ( datetime.datetime(1977, 9, 2, 0, 0), { datetime.timedelta(seconds=7200): {"Asia/Damascus"}, datetime.timedelta(seconds=10800): { "Africa/Cairo", "Egypt", }, }, ), datetime.timedelta(seconds=10800): ( datetime.datetime(2017, 11, 1, 18, 0), { datetime.timedelta(seconds=7200): {"Africa/Khartoum"}, datetime.timedelta(seconds=10800): {"Africa/Juba"}, }, ), }, ), }, ), datetime.timedelta(seconds=10800): ( datetime.datetime(1981, 4, 1, 15, 0), { datetime.timedelta(seconds=10800): ( datetime.datetime(1982, 5, 1, 18, 0), { datetime.timedelta(seconds=10800): { "Africa/Addis_Ababa", "Africa/Asmara", "Africa/Asmera", "Africa/Dar_es_Salaam", "Africa/Djibouti", "Africa/Kampala", "Africa/Mogadishu", "Africa/Nairobi", "Antarctica/Syowa", "Asia/Aden", "Asia/Kuwait", "Asia/Riyadh", "Etc/GMT-3", "Indian/Antananarivo", "Indian/Comoro", "Indian/Mayotte", }, datetime.timedelta(seconds=14400): {"Asia/Baghdad"}, }, ), datetime.timedelta(seconds=14400): ( datetime.datetime(1989, 3, 26, 21, 0), { datetime.timedelta(seconds=10800): ( datetime.datetime(1996, 9, 29, 15, 0), { datetime.timedelta(seconds=7200): {"Europe/Riga"}, datetime.timedelta(seconds=10800): ( datetime.datetime(1998, 3, 29, 15, 0), { datetime.timedelta(seconds=7200): { "Europe/Vilnius" }, datetime.timedelta(seconds=10800): ( datetime.datetime(2000, 3, 26, 21, 0), { datetime.timedelta(seconds=7200): { "Europe/Tallinn" }, datetime.timedelta(seconds=10800): { "Europe/Kaliningrad" }, }, ), }, ), }, ), datetime.timedelta(seconds=14400): ( datetime.datetime(1990, 3, 25, 21, 0), { datetime.timedelta(seconds=10800): ( datetime.datetime(1990, 7, 2, 0, 0), { datetime.timedelta(seconds=7200): { "Europe/Simferopol" }, datetime.timedelta(seconds=10800): { "Europe/Minsk" }, }, ), datetime.timedelta(seconds=14400): ( datetime.datetime(1990, 5, 6, 15, 0), { datetime.timedelta(seconds=10800): { "Europe/Chisinau", "Europe/Tiraspol", }, datetime.timedelta(seconds=14400): ( datetime.datetime(1990, 7, 1, 15, 0), { datetime.timedelta(seconds=10800): { "Europe/Kiev", "Europe/Kyiv", "Europe/Uzhgorod", "Europe/Zaporozhye", }, datetime.timedelta(seconds=14400): { "Europe/Moscow", "W-SU", }, }, ), }, ), }, ), }, ), }, ), datetime.timedelta(seconds=12600): {"Iran", "Asia/Tehran"}, datetime.timedelta(seconds=14400): ( datetime.datetime(1972, 6, 1, 21, 0), { datetime.timedelta(seconds=10800): {"Asia/Bahrain", "Asia/Qatar"}, datetime.timedelta(seconds=14400): ( datetime.datetime(1981, 4, 1, 15, 0), { datetime.timedelta(seconds=14400): ( datetime.datetime(1982, 10, 11, 0, 0), { datetime.timedelta(seconds=14400): { "Asia/Dubai", "Asia/Muscat", "Etc/GMT-4", "Indian/Mahe", "Indian/Reunion", }, datetime.timedelta(seconds=18000): {"Indian/Mauritius"}, }, ), datetime.timedelta(seconds=18000): ( datetime.datetime(1988, 3, 27, 21, 0), { datetime.timedelta(seconds=14400): ( datetime.datetime(2016, 12, 4, 18, 0), { datetime.timedelta(seconds=10800): { "Europe/Volgograd" }, datetime.timedelta(seconds=14400): { "Europe/Saratov" }, }, ), datetime.timedelta(seconds=18000): ( datetime.datetime(1989, 3, 26, 21, 0), { datetime.timedelta(seconds=14400): ( datetime.datetime(1991, 3, 31, 18, 0), { datetime.timedelta(seconds=10800): ( datetime.datetime( 1991, 9, 29, 18, 0 ), { datetime.timedelta( seconds=7200 ): {"Europe/Ulyanovsk"}, datetime.timedelta( seconds=10800 ): {"Europe/Samara"}, }, ), datetime.timedelta(seconds=14400): ( datetime.datetime( 2016, 3, 27, 18, 0 ), { datetime.timedelta( seconds=10800 ): {"Europe/Kirov"}, datetime.timedelta( seconds=14400 ): {"Europe/Astrakhan"}, }, ), }, ), datetime.timedelta(seconds=18000): ( datetime.datetime(1992, 9, 27, 18, 0), { datetime.timedelta(seconds=10800): ( datetime.datetime( 1994, 9, 25, 18, 0 ), { datetime.timedelta( seconds=10800 ): {"Asia/Yerevan"}, datetime.timedelta( seconds=14400 ): {"Asia/Tbilisi"}, }, ), datetime.timedelta(seconds=14400): { "Asia/Baku" }, }, ), }, ), }, ), }, ), }, ), datetime.timedelta(seconds=16200): {"Asia/Kabul"}, datetime.timedelta(seconds=18000): ( datetime.datetime(1981, 4, 1, 15, 0), { datetime.timedelta(seconds=18000): ( datetime.datetime(1981, 10, 2, 0, 0), { datetime.timedelta(seconds=18000): ( datetime.datetime(1996, 1, 1, 21, 0), { datetime.timedelta(seconds=18000): ( datetime.datetime(2002, 4, 8, 0, 0), { datetime.timedelta(seconds=18000): { "Etc/GMT-5", "Indian/Kerguelen", "Indian/Maldives", }, datetime.timedelta(seconds=21600): { "Asia/Karachi" }, }, ), datetime.timedelta(seconds=21600): {"Indian/Chagos"}, }, ), datetime.timedelta(seconds=21600): ( datetime.datetime(1994, 9, 26, 3, 0), { datetime.timedelta(seconds=14400): {"Asia/Aqtau"}, datetime.timedelta(seconds=18000): {"Asia/Atyrau"}, }, ), }, ), datetime.timedelta(seconds=21600): ( datetime.datetime(1981, 10, 1, 12, 0), { datetime.timedelta(seconds=18000): ( datetime.datetime(1992, 3, 29, 18, 0), { datetime.timedelta(seconds=18000): { "Asia/Ashgabat", "Asia/Ashkhabad", }, datetime.timedelta(seconds=21600): { "Asia/Yekaterinburg" }, }, ), datetime.timedelta(seconds=21600): ( datetime.datetime(1989, 3, 26, 15, 0), { datetime.timedelta(seconds=18000): {"Asia/Oral"}, datetime.timedelta(seconds=21600): ( datetime.datetime(1991, 4, 1, 0, 0), { datetime.timedelta(seconds=18000): ( datetime.datetime(1991, 9, 30, 0, 0), { datetime.timedelta(seconds=14400): ( datetime.datetime( 2004, 10, 31, 18, 0 ), { datetime.timedelta( seconds=18000 ): {"Asia/Aqtobe"}, datetime.timedelta( seconds=21600 ): {"Asia/Qostanay"}, }, ), datetime.timedelta(seconds=18000): { "Asia/Qyzylorda" }, }, ), datetime.timedelta(seconds=21600): { "Asia/Samarkand" }, }, ), }, ), }, ), }, ), datetime.timedelta(seconds=19800): ( datetime.datetime(1986, 1, 1, 18, 0), { datetime.timedelta(seconds=19800): ( datetime.datetime(1987, 10, 1, 18, 0), { datetime.timedelta(seconds=19800): ( datetime.datetime(1996, 5, 25, 15, 0), { datetime.timedelta(seconds=19800): { "Asia/Calcutta", "Asia/Kolkata", }, datetime.timedelta(seconds=23400): {"Asia/Colombo"}, }, ), datetime.timedelta(seconds=21600): { "Asia/Thimbu", "Asia/Thimphu", }, }, ), datetime.timedelta(seconds=20700): {"Asia/Kathmandu", "Asia/Katmandu"}, }, ), datetime.timedelta(seconds=21600): ( datetime.datetime(1978, 1, 2, 0, 0), { datetime.timedelta(seconds=21600): ( datetime.datetime(1981, 4, 1, 18, 0), { datetime.timedelta(seconds=21600): ( datetime.datetime(2009, 6, 20, 18, 0), { datetime.timedelta(seconds=21600): ( datetime.datetime(2009, 10, 18, 21, 0), { datetime.timedelta(seconds=18000): { "Antarctica/Mawson" }, datetime.timedelta(seconds=21600): { "Asia/Kashgar", "Asia/Urumqi", "Etc/GMT-6", }, }, ), datetime.timedelta(seconds=25200): { "Asia/Dacca", "Asia/Dhaka", }, }, ), datetime.timedelta(seconds=25200): ( datetime.datetime(1991, 8, 31, 21, 0), { datetime.timedelta(seconds=18000): {"Asia/Bishkek"}, datetime.timedelta(seconds=21600): ( datetime.datetime(1991, 9, 9, 15, 0), { datetime.timedelta(seconds=18000): { "Asia/Dushanbe" }, datetime.timedelta(seconds=21600): ( datetime.datetime(1992, 1, 19, 18, 0), { datetime.timedelta(seconds=18000): { "Asia/Tashkent" }, datetime.timedelta(seconds=21600): ( datetime.datetime( 2005, 3, 27, 15, 0 ), { datetime.timedelta( seconds=21600 ): {"Asia/Almaty"}, datetime.timedelta( seconds=25200 ): {"Asia/Omsk"}, }, ), }, ), }, ), }, ), }, ), datetime.timedelta(seconds=25200): {"Asia/Hovd"}, }, ), datetime.timedelta(seconds=23400): { "Asia/Rangoon", "Asia/Yangon", "Indian/Cocos", }, datetime.timedelta(seconds=25200): ( datetime.datetime(1978, 1, 2, 0, 0), { datetime.timedelta(seconds=25200): ( datetime.datetime(1981, 4, 1, 18, 0), { datetime.timedelta(seconds=25200): ( datetime.datetime(1994, 2, 1, 12, 0), { datetime.timedelta(0): {"Antarctica/Vostok"}, datetime.timedelta(seconds=25200): ( datetime.datetime(2009, 10, 18, 21, 0), { datetime.timedelta(seconds=18000): { "Antarctica/Davis" }, datetime.timedelta(seconds=25200): { "Asia/Bangkok", "Asia/Jakarta", "Asia/Phnom_Penh", "Asia/Vientiane", "Etc/GMT-7", "Indian/Christmas", }, }, ), }, ), datetime.timedelta(seconds=28800): ( datetime.datetime(1993, 5, 23, 18, 0), { datetime.timedelta(seconds=25200): {"Asia/Novosibirsk"}, datetime.timedelta(seconds=28800): ( datetime.datetime(1995, 5, 28, 18, 0), { datetime.timedelta(seconds=25200): { "Asia/Barnaul" }, datetime.timedelta(seconds=28800): ( datetime.datetime(2002, 5, 2, 0, 0), { datetime.timedelta(seconds=25200): { "Asia/Tomsk" }, datetime.timedelta(seconds=28800): ( datetime.datetime( 2010, 3, 28, 15, 0 ), { datetime.timedelta( seconds=25200 ): {"Asia/Novokuznetsk"}, datetime.timedelta( seconds=28800 ): {"Asia/Krasnoyarsk"}, }, ), }, ), }, ), }, ), }, ), datetime.timedelta(seconds=28800): { "Asia/Choibalsan", "Asia/Ulaanbaatar", "Asia/Ulan_Bator", }, }, ), datetime.timedelta(seconds=27000): { "Asia/Kuala_Lumpur", "Asia/Singapore", "Singapore", }, datetime.timedelta(seconds=28800): ( datetime.datetime(1970, 4, 19, 18, 0), { datetime.timedelta(seconds=28800): ( datetime.datetime(1974, 4, 1, 15, 0), { datetime.timedelta(seconds=28800): ( datetime.datetime(1974, 10, 27, 15, 0), { datetime.timedelta(seconds=28800): ( datetime.datetime(1975, 6, 13, 21, 0), { datetime.timedelta(seconds=25200): { "Asia/Ho_Chi_Minh", "Asia/Saigon", }, datetime.timedelta(seconds=28800): ( datetime.datetime(1978, 3, 22, 21, 0), { datetime.timedelta(seconds=28800): ( datetime.datetime( 1981, 4, 1, 21, 0 ), { datetime.timedelta( seconds=28800 ): ( datetime.datetime( 1986, 5, 4, 21, 0 ), { datetime.timedelta( seconds=28800 ): ( datetime.datetime( 1988, 1, 1, 18, 0, ), { datetime.timedelta( seconds=25200 ): { "Asia/Pontianak" }, datetime.timedelta( seconds=28800 ): ( datetime.datetime( 2009, 10, 19, 3, 0, ), { datetime.timedelta( seconds=28800 ): { "Asia/Brunei", "Asia/Kuching", "Asia/Makassar", "Asia/Ujung_Pandang", "Etc/GMT-8", }, datetime.timedelta( seconds=39600 ): { "Antarctica/Casey" }, }, ), }, ), datetime.timedelta( seconds=32400 ): { "Asia/Chongqing", "Asia/Chungking", "Asia/Harbin", "Asia/Shanghai", "PRC", }, }, ), datetime.timedelta( seconds=32400 ): {"Asia/Irkutsk"}, }, ), datetime.timedelta(seconds=32400): { "Asia/Manila" }, }, ), }, ), datetime.timedelta(seconds=32400): { "Australia/Perth", "Australia/West", }, }, ), datetime.timedelta(seconds=32400): {"Asia/Taipei", "ROC"}, }, ), datetime.timedelta(seconds=32400): { "Asia/Hong_Kong", "Asia/Macao", "Asia/Macau", "Hongkong", }, }, ), datetime.timedelta(seconds=31500): {"Australia/Eucla"}, datetime.timedelta(seconds=32400): ( datetime.datetime(1976, 5, 3, 18, 0), { datetime.timedelta(seconds=28800): {"Asia/Dili"}, datetime.timedelta(seconds=32400): ( datetime.datetime(1981, 4, 1, 18, 0), { datetime.timedelta(seconds=32400): ( datetime.datetime(1987, 5, 10, 15, 0), { datetime.timedelta(seconds=32400): ( datetime.datetime(2015, 8, 15, 15, 0), { datetime.timedelta(seconds=30600): { "Asia/Pyongyang" }, datetime.timedelta(seconds=32400): { "Asia/Jayapura", "Asia/Tokyo", "Etc/GMT-9", "Japan", "Pacific/Palau", }, }, ), datetime.timedelta(seconds=36000): { "Asia/Seoul", "ROK", }, }, ), datetime.timedelta(seconds=36000): ( datetime.datetime(2004, 1, 1, 21, 0), { datetime.timedelta(seconds=32400): ( datetime.datetime(2014, 10, 27, 0, 0), { datetime.timedelta(seconds=28800): { "Asia/Chita" }, datetime.timedelta(seconds=32400): { "Asia/Yakutsk" }, }, ), datetime.timedelta(seconds=36000): {"Asia/Khandyga"}, }, ), datetime.timedelta(seconds=43200): {"Asia/Ust-Nera"}, }, ), }, ), datetime.timedelta(seconds=34200): ( datetime.datetime(1971, 10, 31, 21, 0), { datetime.timedelta(seconds=34200): { "Australia/Darwin", "Australia/North", }, datetime.timedelta(seconds=37800): ( datetime.datetime(1982, 3, 7, 18, 0), { datetime.timedelta(seconds=34200): { "Australia/Adelaide", "Australia/South", }, datetime.timedelta(seconds=37800): { "Australia/Broken_Hill", "Australia/Yancowinna", }, }, ), }, ), datetime.timedelta(seconds=36000): ( datetime.datetime(1970, 4, 26, 15, 0), { datetime.timedelta(seconds=36000): ( datetime.datetime(1971, 10, 31, 21, 0), { datetime.timedelta(seconds=36000): ( datetime.datetime(1981, 3, 2, 0, 0), { datetime.timedelta(seconds=36000): ( datetime.datetime(1981, 4, 1, 18, 0), { datetime.timedelta(seconds=36000): ( datetime.datetime(2014, 12, 28, 21, 0), { datetime.timedelta(seconds=36000): { "Antarctica/DumontDUrville", "Etc/GMT-10", "Pacific/Chuuk", "Pacific/Port_Moresby", "Pacific/Truk", "Pacific/Yap", }, datetime.timedelta(seconds=39600): { "Pacific/Bougainville" }, }, ), datetime.timedelta(seconds=39600): { "Asia/Vladivostok" }, }, ), datetime.timedelta(seconds=37800): { "Australia/LHI", "Australia/Lord_Howe", }, }, ), datetime.timedelta(seconds=39600): ( datetime.datetime(1972, 10, 29, 21, 0), { datetime.timedelta(seconds=36000): ( datetime.datetime(1992, 10, 26, 0, 0), { datetime.timedelta(seconds=36000): { "Australia/Brisbane", "Australia/Queensland", }, datetime.timedelta(seconds=39600): { "Australia/Lindeman" }, }, ), datetime.timedelta(seconds=39600): ( datetime.datetime(1982, 3, 7, 18, 0), { datetime.timedelta(seconds=36000): { "Australia/Melbourne", "Australia/Victoria", }, datetime.timedelta(seconds=39600): { "Australia/ACT", "Australia/Canberra", "Australia/NSW", "Australia/Sydney", }, }, ), }, ), }, ), datetime.timedelta(seconds=39600): {"Pacific/Guam", "Pacific/Saipan"}, }, ), datetime.timedelta(seconds=39600): ( datetime.datetime(1970, 3, 8, 15, 0), { datetime.timedelta(seconds=36000): ( datetime.datetime(2010, 4, 4, 21, 0), { datetime.timedelta(seconds=36000): { "Australia/Currie", "Australia/Hobart", "Australia/Tasmania", }, datetime.timedelta(seconds=39600): {"Antarctica/Macquarie"}, }, ), datetime.timedelta(seconds=39600): ( datetime.datetime(1973, 12, 23, 15, 0), { datetime.timedelta(seconds=39600): ( datetime.datetime(1977, 12, 4, 15, 0), { datetime.timedelta(seconds=39600): ( datetime.datetime(1981, 4, 1, 18, 0), { datetime.timedelta(seconds=39600): { "Etc/GMT-11", "Pacific/Guadalcanal", "Pacific/Pohnpei", "Pacific/Ponape", }, datetime.timedelta(seconds=43200): ( datetime.datetime(1997, 3, 30, 15, 0), { datetime.timedelta(seconds=39600): { "Asia/Sakhalin" }, datetime.timedelta(seconds=43200): ( datetime.datetime( 2014, 10, 26, 18, 0 ), { datetime.timedelta( seconds=36000 ): {"Asia/Magadan"}, datetime.timedelta( seconds=39600 ): {"Asia/Srednekolymsk"}, }, ), }, ), }, ), datetime.timedelta(seconds=43200): {"Pacific/Noumea"}, }, ), datetime.timedelta(seconds=43200): {"Pacific/Efate"}, }, ), }, ), datetime.timedelta(seconds=41400): ( datetime.datetime(1974, 10, 27, 15, 0), { datetime.timedelta(seconds=41400): {"Pacific/Nauru"}, datetime.timedelta(seconds=45000): {"Pacific/Norfolk"}, }, ), datetime.timedelta(seconds=43200): ( datetime.datetime(1974, 11, 3, 21, 0), { datetime.timedelta(seconds=43200): ( datetime.datetime(1981, 4, 1, 15, 0), { datetime.timedelta(seconds=43200): ( datetime.datetime(1998, 11, 1, 18, 0), { datetime.timedelta(seconds=43200): ( datetime.datetime(1999, 1, 1, 18, 0), { datetime.timedelta(seconds=39600): { "Pacific/Kosrae" }, datetime.timedelta(seconds=43200): { "Etc/GMT-12", "Pacific/Funafuti", "Pacific/Majuro", "Pacific/Tarawa", "Pacific/Wake", "Pacific/Wallis", }, }, ), datetime.timedelta(seconds=46800): {"Pacific/Fiji"}, }, ), datetime.timedelta(seconds=46800): {"Asia/Kamchatka"}, }, ), datetime.timedelta(seconds=46800): { "Antarctica/McMurdo", "Antarctica/South_Pole", "NZ", "Pacific/Auckland", }, }, ), datetime.timedelta(seconds=45900): {"NZ-CHAT", "Pacific/Chatham"}, datetime.timedelta(seconds=46800): ( datetime.datetime(1981, 4, 1, 15, 0), { datetime.timedelta(seconds=46800): ( datetime.datetime(1999, 10, 7, 21, 0), { datetime.timedelta(seconds=46800): {"Etc/GMT-13"}, datetime.timedelta(seconds=50400): {"Pacific/Tongatapu"}, }, ), datetime.timedelta(seconds=50400): {"Asia/Anadyr"}, }, ), datetime.timedelta(seconds=50400): {"Etc/GMT-14"}, }, ) __all__ = ["lookup"] icalendar-6.3.1/src/icalendar/timezone/provider.py000066400000000000000000000030761501302773300222210ustar00rootroot00000000000000"""The interface for timezone implementations.""" from __future__ import annotations from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: from datetime import datetime, tzinfo from dateutil.rrule import rrule from icalendar import prop class TZProvider(ABC): """Interface for timezone implementations.""" @property @abstractmethod def name(self) -> str: """The name of the implementation.""" @abstractmethod def localize_utc(self, dt: datetime) -> datetime: """Return the datetime in UTC.""" @abstractmethod def localize(self, dt: datetime, tz: tzinfo) -> datetime: """Localize a datetime to a timezone.""" @abstractmethod def knows_timezone_id(self, id: str) -> bool: """Whether the timezone is already cached by the implementation.""" @abstractmethod def fix_rrule_until(self, rrule: rrule, ical_rrule: prop.vRecur) -> None: """Make sure the until value works for the rrule generated from the ical_rrule.""" @abstractmethod def create_timezone(self, name: str, transition_times, transition_info) -> tzinfo: """Create a pytz timezone file given information.""" @abstractmethod def timezone(self, name: str) -> Optional[tzinfo]: """Return a timezone with a name or None if we cannot find it.""" @abstractmethod def uses_pytz(self) -> bool: """Whether we use pytz.""" @abstractmethod def uses_zoneinfo(self) -> bool: """Whether we use zoneinfo.""" __all__ = ["TZProvider"] icalendar-6.3.1/src/icalendar/timezone/pytz.py000066400000000000000000000044151501302773300213730ustar00rootroot00000000000000"""Use pytz timezones.""" from __future__ import annotations import pytz from .. import cal from datetime import datetime, tzinfo from pytz.tzinfo import DstTzInfo from typing import Optional from .provider import TZProvider from icalendar import prop from dateutil.rrule import rrule class PYTZ(TZProvider): """Provide icalendar with timezones from pytz.""" name = "pytz" def localize_utc(self, dt: datetime) -> datetime: """Return the datetime in UTC.""" if getattr(dt, "tzinfo", False) and dt.tzinfo is not None: return dt.astimezone(pytz.utc) # assume UTC for naive datetime instances return pytz.utc.localize(dt) def localize(self, dt: datetime, tz: tzinfo) -> datetime: """Localize a datetime to a timezone.""" return tz.localize(dt) def knows_timezone_id(self, id: str) -> bool: """Whether the timezone is already cached by the implementation.""" return id in pytz.all_timezones def fix_rrule_until(self, rrule: rrule, ical_rrule: prop.vRecur) -> None: """Make sure the until value works for the rrule generated from the ical_rrule.""" if not {"UNTIL", "COUNT"}.intersection(ical_rrule.keys()): # pytz.timezones don't know any transition dates after 2038 # either rrule._until = datetime(2038, 12, 31, tzinfo=pytz.UTC) def create_timezone(self, tz: cal.Timezone) -> tzinfo: """Create a pytz timezone from the given information.""" transition_times, transition_info = tz.get_transitions() name = tz.tz_name cls = type( name, (DstTzInfo,), { "zone": name, "_utc_transition_times": transition_times, "_transition_info": transition_info, }, ) return cls() def timezone(self, name: str) -> Optional[tzinfo]: """Return a timezone with a name or None if we cannot find it.""" try: return pytz.timezone(name) except pytz.UnknownTimeZoneError: pass def uses_pytz(self) -> bool: """Whether we use pytz.""" return True def uses_zoneinfo(self) -> bool: """Whether we use zoneinfo.""" return False __all__ = ["PYTZ"] icalendar-6.3.1/src/icalendar/timezone/tzid.py000066400000000000000000000077751501302773300213530ustar00rootroot00000000000000"""This module identifies timezones. Normally, timezones have ids. This is a way to access the ids if you have a datetime.tzinfo object. """ from __future__ import annotations from collections import defaultdict from pathlib import Path from typing import TYPE_CHECKING, Optional from dateutil.tz import tz from icalendar.timezone import equivalent_timezone_ids_result if TYPE_CHECKING: from datetime import datetime, tzinfo DATEUTIL_UTC = tz.gettz("UTC") DATEUTIL_UTC_PATH : Optional[str] = getattr(DATEUTIL_UTC, "_filename", None) DATEUTIL_ZONEINFO_PATH = ( None if DATEUTIL_UTC_PATH is None else Path(DATEUTIL_UTC_PATH).parent ) def tzids_from_tzinfo(tzinfo: Optional[tzinfo]) -> tuple[str]: """Get several timezone ids if we can identify the timezone. >>> import zoneinfo >>> from icalendar.timezone.tzid import tzids_from_tzinfo >>> tzids_from_tzinfo(zoneinfo.ZoneInfo("Arctic/Longyearbyen")) ('Arctic/Longyearbyen', 'Atlantic/Jan_Mayen', 'Europe/Berlin', 'Europe/Budapest', 'Europe/Copenhagen', 'Europe/Oslo', 'Europe/Stockholm', 'Europe/Vienna') >>> from dateutil.tz import gettz >>> tzids_from_tzinfo(gettz("Europe/Berlin")) ('Europe/Berlin', 'Arctic/Longyearbyen', 'Atlantic/Jan_Mayen', 'Europe/Budapest', 'Europe/Copenhagen', 'Europe/Oslo', 'Europe/Stockholm', 'Europe/Vienna') """ # The example might need to change if you recreate the lookup tree if tzinfo is None: return () if hasattr(tzinfo, "zone"): return get_equivalent_tzids(tzinfo.zone) # pytz implementation if hasattr(tzinfo, "key"): return get_equivalent_tzids(tzinfo.key) # ZoneInfo implementation if isinstance(tzinfo, tz._tzicalvtz): # noqa: SLF001 return get_equivalent_tzids(tzinfo._tzid) # noqa: SLF001 if isinstance(tzinfo, tz.tzstr): return get_equivalent_tzids(tzinfo._s) # noqa: SLF001 if hasattr(tzinfo, "_filename"): # dateutil.tz.tzfile # noqa: SIM102 if DATEUTIL_ZONEINFO_PATH is not None: # tzfile('/usr/share/zoneinfo/Europe/Berlin') path = tzinfo._filename # noqa: SLF001 if path.startswith(str(DATEUTIL_ZONEINFO_PATH)): tzid = str(Path(path).relative_to(DATEUTIL_ZONEINFO_PATH)) return get_equivalent_tzids(tzid) return get_equivalent_tzids(path) if isinstance(tzinfo, tz.tzutc): return get_equivalent_tzids("UTC") return () def tzid_from_tzinfo(tzinfo: Optional[tzinfo]) -> Optional[str]: """Retrieve the timezone id from the tzinfo object. Some timezones are equivalent. Thus, we might return one ID that is equivelant to others. """ tzids = tzids_from_tzinfo(tzinfo) if "UTC" in tzids: return "UTC" if not tzids: return None return tzids[0] def tzid_from_dt(dt: datetime) -> Optional[str]: """Retrieve the timezone id from the datetime object.""" tzid = tzid_from_tzinfo(dt.tzinfo) if tzid is None: return dt.tzname() return tzid _EQUIVALENT_IDS : dict[str, set[str]] = defaultdict(set) def _add_equivalent_ids(value:tuple|dict|set): """This adds equivalent ids/ As soon as one timezone implementation used claims their equivalence, they are considered equivalent. Have a look at icalendar.timezone.equivalent_timezone_ids. """ if isinstance(value, set): for tzid in value: _EQUIVALENT_IDS[tzid].update(value) elif isinstance(value, tuple): _add_equivalent_ids(value[1]) elif isinstance(value, dict): for value in value.values(): _add_equivalent_ids(value) else: raise TypeError(f"Expected tuple, dict or set, not {value.__class__.__name__}: {value!r}") _add_equivalent_ids(equivalent_timezone_ids_result.lookup) def get_equivalent_tzids(tzid: str) -> tuple[str]: """This returns the tzids which are equivalent to this one.""" ids = _EQUIVALENT_IDS.get(tzid, set()) return (tzid,) + tuple(sorted(ids - {tzid})) __all__ = ["tzid_from_tzinfo", "tzid_from_dt", "tzids_from_tzinfo"] icalendar-6.3.1/src/icalendar/timezone/tzp.py000066400000000000000000000113751501302773300212050ustar00rootroot00000000000000from __future__ import annotations from typing import TYPE_CHECKING, Optional, Union from icalendar.tools import to_datetime from .windows_to_olson import WINDOWS_TO_OLSON if TYPE_CHECKING: import datetime from dateutil.rrule import rrule from icalendar import cal, prop from .provider import TZProvider DEFAULT_TIMEZONE_PROVIDER = "zoneinfo" class TZP: """This is the timezone provider proxy. If you would like to have another timezone implementation, you can create a new one and pass it to this proxy. All of icalendar will then use this timezone implementation. """ def __init__(self, provider: Union[str, TZProvider] = DEFAULT_TIMEZONE_PROVIDER): """Create a new timezone implementation proxy.""" self.use(provider) def use_pytz(self) -> None: """Use pytz as the timezone provider.""" from .pytz import PYTZ self._use(PYTZ()) def use_zoneinfo(self) -> None: """Use zoneinfo as the timezone provider.""" from .zoneinfo import ZONEINFO self._use(ZONEINFO()) def _use(self, provider: TZProvider) -> None: """Use a timezone implementation.""" self.__tz_cache = {} self.__provider = provider def use(self, provider: Union[str, TZProvider]): """Switch to a different timezone provider.""" if isinstance(provider, str): use_provider = getattr(self, f"use_{provider}", None) if use_provider is None: raise ValueError( f"Unknown provider {provider}. Use 'pytz' or 'zoneinfo'." ) use_provider() else: self._use(provider) def use_default(self): """Use the default timezone provider.""" self.use(DEFAULT_TIMEZONE_PROVIDER) def localize_utc(self, dt: datetime.date) -> datetime.datetime: """Return the datetime in UTC. If the datetime has no timezone, set UTC as its timezone. """ return self.__provider.localize_utc(to_datetime(dt)) def localize( self, dt: datetime.date, tz: Union[datetime.tzinfo, str, None] ) -> datetime.datetime: """Localize a datetime to a timezone.""" if isinstance(tz, str): tz = self.timezone(tz) if tz is None: return dt.replace(tzinfo=None) return self.__provider.localize(to_datetime(dt), tz) def cache_timezone_component(self, timezone_component: cal.Timezone) -> None: """Cache the timezone that is created from a timezone component if it is not already known. This can influence the result from timezone(): Once cached, the custom timezone is returned from timezone(). """ _unclean_id = timezone_component["TZID"] _id = self.clean_timezone_id(_unclean_id) if ( not self.__provider.knows_timezone_id(_id) and not self.__provider.knows_timezone_id(_unclean_id) and _id not in self.__tz_cache ): self.__tz_cache[_id] = timezone_component.to_tz(self, lookup_tzid=False) def fix_rrule_until(self, rrule: rrule, ical_rrule: prop.vRecur) -> None: """Make sure the until value works.""" self.__provider.fix_rrule_until(rrule, ical_rrule) def create_timezone(self, timezone_component: cal.Timezone) -> datetime.tzinfo: """Create a timezone from a timezone component. This component will not be cached. """ return self.__provider.create_timezone(timezone_component) def clean_timezone_id(self, tzid: str) -> str: """Return a clean version of the timezone id. Timezone ids can be a bit unclean, starting with a / for example. Internally, we should use this to identify timezones. """ return tzid.strip("/") def timezone(self, tz_id: str) -> Optional[datetime.tzinfo]: """Return a timezone with an id or None if we cannot find it.""" _unclean_id = tz_id tz_id = self.clean_timezone_id(tz_id) tz = self.__provider.timezone(tz_id) if tz is not None: return tz if tz_id in WINDOWS_TO_OLSON: tz = self.__provider.timezone(WINDOWS_TO_OLSON[tz_id]) return tz or self.__provider.timezone(_unclean_id) or self.__tz_cache.get(tz_id) def uses_pytz(self) -> bool: """Whether we use pytz at all.""" return self.__provider.uses_pytz() def uses_zoneinfo(self) -> bool: """Whether we use zoneinfo.""" return self.__provider.uses_zoneinfo() @property def name(self) -> str: """The name of the timezone component used.""" return self.__provider.name def __repr__(self) -> str: return f"{self.__class__.__name__}({repr(self.name)})" __all__ = ["TZP"] icalendar-6.3.1/src/icalendar/timezone/windows_to_olson.py000066400000000000000000000130001501302773300237610ustar00rootroot00000000000000"""This module contains mappings from Windows timezone identifiers to Olson timezone identifiers. The data is taken from the unicode consortium [0], the proposal and rationale for this mapping is also available at the unicode consortium [1]. [0] https://www.unicode.org/cldr/cldr-aux/charts/29/supplemental/zone_tzid.html [1] https://cldr.unicode.org/development/development-process/design-proposals/extended-windows-olson-zid-mapping # noqa """ WINDOWS_TO_OLSON = { "AUS Central Standard Time": "Australia/Darwin", "AUS Eastern Standard Time": "Australia/Sydney", "Afghanistan Standard Time": "Asia/Kabul", "Alaskan Standard Time": "America/Anchorage", "Arab Standard Time": "Asia/Riyadh", "Arabian Standard Time": "Asia/Dubai", "Arabic Standard Time": "Asia/Baghdad", "Argentina Standard Time": "America/Argentina/Buenos_Aires", "Atlantic Standard Time": "America/Halifax", "Azerbaijan Standard Time": "Asia/Baku", "Azores Standard Time": "Atlantic/Azores", "Bahia Standard Time": "America/Bahia", "Bangladesh Standard Time": "Asia/Dhaka", "Belarus Standard Time": "Europe/Minsk", "Canada Central Standard Time": "America/Regina", "Cape Verde Standard Time": "Atlantic/Cape_Verde", "Caucasus Standard Time": "Asia/Yerevan", "Cen. Australia Standard Time": "Australia/Adelaide", "Central America Standard Time": "America/Guatemala", "Central Asia Standard Time": "Asia/Almaty", "Central Brazilian Standard Time": "America/Cuiaba", "Central Europe Standard Time": "Europe/Budapest", "Central European Standard Time": "Europe/Warsaw", "Central Pacific Standard Time": "Pacific/Guadalcanal", "Central Standard Time": "America/Chicago", "Central Standard Time (Mexico)": "America/Mexico_City", "China Standard Time": "Asia/Shanghai", "Dateline Standard Time": "Etc/GMT+12", "E. Africa Standard Time": "Africa/Nairobi", "E. Australia Standard Time": "Australia/Brisbane", "E. Europe Standard Time": "Europe/Chisinau", "E. South America Standard Time": "America/Sao_Paulo", "Eastern Standard Time": "America/New_York", "Eastern Standard Time (Mexico)": "America/Cancun", "Egypt Standard Time": "Africa/Cairo", "Ekaterinburg Standard Time": "Asia/Yekaterinburg", "FLE Standard Time": "Europe/Kyiv", "Fiji Standard Time": "Pacific/Fiji", "GMT Standard Time": "Europe/London", "GTB Standard Time": "Europe/Bucharest", "Georgian Standard Time": "Asia/Tbilisi", "Greenland Standard Time": "America/Nuuk", "Greenwich Standard Time": "Atlantic/Reykjavik", "Hawaiian Standard Time": "Pacific/Honolulu", "India Standard Time": "Asia/Kolkata", "Iran Standard Time": "Asia/Tehran", "Israel Standard Time": "Asia/Jerusalem", "Jordan Standard Time": "Asia/Amman", "Kaliningrad Standard Time": "Europe/Kaliningrad", "Korea Standard Time": "Asia/Seoul", "Libya Standard Time": "Africa/Tripoli", "Line Islands Standard Time": "Pacific/Kiritimati", "Magadan Standard Time": "Asia/Magadan", "Mauritius Standard Time": "Indian/Mauritius", "Middle East Standard Time": "Asia/Beirut", "Montevideo Standard Time": "America/Montevideo", "Morocco Standard Time": "Africa/Casablanca", "Mountain Standard Time": "America/Denver", "Mountain Standard Time (Mexico)": "America/Chihuahua", "Myanmar Standard Time": "Asia/Yangon", "N. Central Asia Standard Time": "Asia/Novosibirsk", "Namibia Standard Time": "Africa/Windhoek", "Nepal Standard Time": "Asia/Kathmandu", "New Zealand Standard Time": "Pacific/Auckland", "Newfoundland Standard Time": "America/St_Johns", "North Asia East Standard Time": "Asia/Irkutsk", "North Asia Standard Time": "Asia/Krasnoyarsk", "North Korea Standard Time": "Asia/Pyongyang", "Pacific SA Standard Time": "America/Santiago", "Pacific Standard Time": "America/Los_Angeles", "Pakistan Standard Time": "Asia/Karachi", "Paraguay Standard Time": "America/Asuncion", "Romance Standard Time": "Europe/Paris", "Russia Time Zone 10": "Asia/Srednekolymsk", "Russia Time Zone 11": "Asia/Kamchatka", "Russia Time Zone 3": "Europe/Samara", "Russian Standard Time": "Europe/Moscow", "SA Eastern Standard Time": "America/Cayenne", "SA Pacific Standard Time": "America/Bogota", "SA Western Standard Time": "America/La_Paz", "SE Asia Standard Time": "Asia/Bangkok", "Samoa Standard Time": "Pacific/Apia", "Singapore Standard Time": "Asia/Singapore", "South Africa Standard Time": "Africa/Johannesburg", "Sri Lanka Standard Time": "Asia/Colombo", "Syria Standard Time": "Asia/Damascus", "Taipei Standard Time": "Asia/Taipei", "Tasmania Standard Time": "Australia/Hobart", "Tokyo Standard Time": "Asia/Tokyo", "Tonga Standard Time": "Pacific/Tongatapu", "Turkey Standard Time": "Europe/Istanbul", "US Eastern Standard Time": "America/Indiana/Indianapolis", "US Mountain Standard Time": "America/Phoenix", "UTC": "Etc/GMT", "UTC+12": "Etc/GMT-12", "UTC-02": "Etc/GMT+2", "UTC-11": "Etc/GMT+11", "Ulaanbaatar Standard Time": "Asia/Ulaanbaatar", "Venezuela Standard Time": "America/Caracas", "Vladivostok Standard Time": "Asia/Vladivostok", "W. Australia Standard Time": "Australia/Perth", "W. Central Africa Standard Time": "Africa/Lagos", "W. Europe Standard Time": "Europe/Berlin", "West Asia Standard Time": "Asia/Tashkent", "West Pacific Standard Time": "Pacific/Port_Moresby", "Yakutsk Standard Time": "Asia/Yakutsk", } icalendar-6.3.1/src/icalendar/timezone/zoneinfo.py000066400000000000000000000116511501302773300222140ustar00rootroot00000000000000"""Use zoneinfo timezones""" from __future__ import annotations from icalendar.tools import is_date, to_datetime try: import zoneinfo except ImportError: from backports import zoneinfo # type: ignore # noqa: PGH003 import copy import copyreg import functools from datetime import datetime, tzinfo from io import StringIO from typing import TYPE_CHECKING, Optional from dateutil.rrule import rrule, rruleset from dateutil.tz import tzical from dateutil.tz.tz import _tzicalvtz from .provider import TZProvider if TYPE_CHECKING: from icalendar import cal, prop from icalendar.prop import vDDDTypes class ZONEINFO(TZProvider): """Provide icalendar with timezones from zoneinfo.""" name = "zoneinfo" utc = zoneinfo.ZoneInfo("UTC") _available_timezones = zoneinfo.available_timezones() def localize(self, dt: datetime, tz: zoneinfo.ZoneInfo) -> datetime: """Localize a datetime to a timezone.""" return dt.replace(tzinfo=tz) def localize_utc(self, dt: datetime) -> datetime: """Return the datetime in UTC.""" if getattr(dt, "tzinfo", False) and dt.tzinfo is not None: return dt.astimezone(self.utc) return self.localize(dt, self.utc) def timezone(self, name: str) -> Optional[tzinfo]: """Return a timezone with a name or None if we cannot find it.""" try: return zoneinfo.ZoneInfo(name) except zoneinfo.ZoneInfoNotFoundError: pass except ValueError: # ValueError: ZoneInfo keys may not be absolute paths, got: /Europe/CUSTOM pass def knows_timezone_id(self, id: str) -> bool: """Whether the timezone is already cached by the implementation.""" return id in self._available_timezones def fix_rrule_until(self, rrule: rrule, ical_rrule: prop.vRecur) -> None: """Make sure the until value works for the rrule generated from the ical_rrule.""" if not {"UNTIL", "COUNT"}.intersection(ical_rrule.keys()): # zoninfo does not know any transition dates after 2038 rrule._until = datetime(2038, 12, 31, tzinfo=self.utc) def create_timezone(self, tz: cal.Timezone) -> tzinfo: """Create a timezone from the given information.""" try: return self._create_timezone(tz) except ValueError: # We might have a custom component in there. # see https://github.com/python/cpython/issues/120217 tz = copy.deepcopy(tz) for sub in tz.walk(): for attr in list(sub.keys()): if attr.lower().startswith("x-"): sub.pop(attr) for sub in tz.subcomponents: start : vDDDTypes = sub.get("DTSTART") if start and is_date(start.dt): # ValueError: Unsupported DTSTART param in VTIMEZONE: VALUE=DATE sub.DTSTART = to_datetime(start.dt) return self._create_timezone(tz) def _create_timezone(self, tz: cal.Timezone) -> tzinfo: """Create a timezone and maybe fail""" file = StringIO(tz.to_ical().decode("UTF-8", "replace")) return tzical(file).get() def uses_pytz(self) -> bool: """Whether we use pytz.""" return False def uses_zoneinfo(self) -> bool: """Whether we use zoneinfo.""" return True def pickle_tzicalvtz(tzicalvtz: tz._tzicalvtz): """Because we use dateutil.tzical, we need to make it pickle-able.""" return _tzicalvtz, (tzicalvtz._tzid, tzicalvtz._comps) copyreg.pickle(_tzicalvtz, pickle_tzicalvtz) def pickle_rrule_with_cache(self: rrule): """Make sure we can also pickle rrules that cache. This is mainly copied from rrule.replace. """ new_kwargs = { "interval": self._interval, "count": self._count, "dtstart": self._dtstart, "freq": self._freq, "until": self._until, "wkst": self._wkst, "cache": False if self._cache is None else True, } new_kwargs.update(self._original_rule) # from https://stackoverflow.com/a/64915638/1320237 return functools.partial(rrule, new_kwargs.pop("freq"), **new_kwargs), () copyreg.pickle(rrule, pickle_rrule_with_cache) def pickle_rruleset_with_cache(rs: rruleset): """Pickle an rruleset.""" # self._rrule = [] # self._rdate = [] # self._exrule = [] # self._exdate = [] return unpickle_rruleset_with_cache, ( rs._rrule, rs._rdate, rs._exrule, rs._exdate, False if rs._cache is None else True, ) def unpickle_rruleset_with_cache(rrule, rdate, exrule, exdate, cache): """unpickling the rruleset.""" rs = rruleset(cache) for o in rrule: rs.rrule(o) for o in rdate: rs.rdate(o) for o in exrule: rs.exrule(o) for o in exdate: rs.exdate(o) return rs copyreg.pickle(rruleset, pickle_rruleset_with_cache) __all__ = ["ZONEINFO"] icalendar-6.3.1/src/icalendar/tools.py000066400000000000000000000057631501302773300177020ustar00rootroot00000000000000"""Utility functions for icalendar.""" from __future__ import annotations import random from datetime import date, datetime, tzinfo from string import ascii_letters, digits from warnings import warn from icalendar.parser_tools import to_unicode from .error import WillBeRemovedInVersion7 class UIDGenerator: """Use this only if you're too lazy to create real UUIDs. .. deprecated:: 6.2.1 Use the Python standard library's :func:`uuid.uuid4` instead. """ chars = list(ascii_letters + digits) @staticmethod def rnd_string(length=16) -> str: """Generates a string with random characters of length. .. deprecated:: 6.2.1 Use the Python standard library's :func:`uuid.uuid4` instead. """ warn( "Use https://docs.python.org/3/library/uuid.html#uuid.uuid4 instead.", WillBeRemovedInVersion7, stacklevel=1 ) return "".join([random.choice(UIDGenerator.chars) for _ in range(length)]) @staticmethod def uid(host_name="example.com", unique=""): """Generates a unique ID consisting of ``datetime-uniquevalue@host``. For example: .. code-block:: text 20050105T225746Z-HKtJMqUgdO0jDUwm@example.com .. deprecated:: 6.2.1 Use the Python standard library's :func:`uuid.uuid5` instead. """ from icalendar.prop import vDatetime, vText warn( "Use https://docs.python.org/3/library/uuid.html#uuid.uuid5 instead.", WillBeRemovedInVersion7, stacklevel=1 ) host_name = to_unicode(host_name) unique = unique or UIDGenerator.rnd_string() today = to_unicode(vDatetime(datetime.today()).to_ical()) return vText(f"{today}-{unique}@{host_name}") def is_date(dt: date) -> bool: """Whether this is a date and not a datetime.""" return isinstance(dt, date) and not isinstance(dt, datetime) def is_datetime(dt: date) -> bool: """Whether this is a date and not a datetime.""" return isinstance(dt, datetime) def to_datetime(dt: date) -> datetime: """Make sure we have a datetime, not a date.""" if is_date(dt): return datetime(dt.year, dt.month, dt.day) # noqa: DTZ001 return dt def is_pytz(tz: tzinfo): """Whether the timezone requires localize() and normalize().""" return hasattr(tz, "localize") def is_pytz_dt(dt: date): """Whether the time requires localize() and normalize().""" return is_datetime(dt) and is_pytz(dt.tzinfo) def normalize_pytz(dt: date): """We have to normalize the time after a calculation if we use pytz. pytz requires this function to be used in order to correctly calculate the timezone's offset after calculations. """ if is_pytz_dt(dt): return dt.tzinfo.normalize(dt) return dt __all__ = [ "UIDGenerator", "is_date", "is_datetime", "to_datetime", "is_pytz", "is_pytz_dt", "normalize_pytz", ] icalendar-6.3.1/src/icalendar/version.py000066400000000000000000000006011501302773300202110ustar00rootroot00000000000000"""Version file as a stable interface for the generated _version.py file.""" try: from ._version import __version__, __version_tuple__, version, version_tuple except ModuleNotFoundError: __version__ = version = "0.0.0dev0" __version_tuple__ = version_tuple = (0, 0, 0, "dev0") __all__ = [ "__version__", "version", "__version_tuple__", "version_tuple", ] icalendar-6.3.1/tox.ini000066400000000000000000000023451501302773300147630ustar00rootroot00000000000000# to run for a specific environment, use ``tox -e ENVNAME`` [tox] envlist = py38,py39,py310,py311,py312,py313,pypy3,docs,nopytz # Note: the 'docs' env creates a 'build' directory which may interfere in strange ways # with the other environments. You might see this when you run the tests in parallel. # See https://github.com/collective/icalendar/pull/359#issuecomment-1214150269 [testenv] usedevelop=True deps = -e .[test] commands = coverage run --branch --source=src/icalendar --omit=*/tests/hypothesis/* --omit=*/tests/fuzzed/* --module pytest [] coverage report coverage html coverage xml [testenv:nopytz] # install with dependencies usedevelop = False # use lowest version basepython = python3.8 deps = pytest coverage hypothesis commands = coverage run --branch --source=src/icalendar --omit=*/tests/hypothesis/* --omit=*/tests/fuzzed/* --module pytest [] coverage report coverage html coverage xml [testenv:docs] deps = -r {toxinidir}/requirements_docs.txt setuptools changedir = docs allowlist_externals = make commands = make clean html [testenv:ruff] # use tox -e ruff to format the code base deps = ruff skip_install = True commands = ruff format ruff check --fix