pax_global_header00006660000000000000000000000064146764147320014530gustar00rootroot0000000000000052 comment=e4af34587478a74674cc5b2c0784a88fd35897aa x-wr-timezone-1.0.1/000077500000000000000000000000001467641473200142545ustar00rootroot00000000000000x-wr-timezone-1.0.1/.github/000077500000000000000000000000001467641473200156145ustar00rootroot00000000000000x-wr-timezone-1.0.1/.github/FUNDING.yml000066400000000000000000000015101467641473200174260ustar00rootroot00000000000000# These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] - niccokunzmann polar: niccokunzmann patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # 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 otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] x-wr-timezone-1.0.1/.github/dependabot.yml000066400000000000000000000007661467641473200204550ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" x-wr-timezone-1.0.1/.github/workflows/000077500000000000000000000000001467641473200176515ustar00rootroot00000000000000x-wr-timezone-1.0.1/.github/workflows/tests.yml000066400000000000000000000072601467641473200215430ustar00rootroot00000000000000name: tests on: push: branches: - main tags: - v* pull_request: workflow_dispatch: jobs: run-tests: strategy: matrix: config: # [Python version, tox env] - ["3.9", "py39"] - ["3.10", "py310"] - ["3.11", "py311"] - ["3.12", "py312"] runs-on: ubuntu-latest name: ${{ matrix.config[1] }} steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.config[0] }} - name: Pip cache uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ matrix.config[0] }}-${{ hashFiles('setup.*', 'tox.ini') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.config[0] }}- ${{ runner.os }}-pip- - name: Install dependencies run: | python -m pip install --upgrade pip pip install tox - name: Test run: | test/test_code_quality.sh tox -e ${{ matrix.config[1] }} -- --x-wr-timezone=all deploy-tag-to-pypi: # only deploy on tags, see https://stackoverflow.com/a/58478262/1320237 if: startsWith(github.ref, 'refs/tags/v') needs: - run-tests runs-on: ubuntu-latest # This environment stores the TWINE_USERNAME and TWINE_PASSWORD # see https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment environment: name: PyPI url: https://pypi.org/project/x-wr-timezone/ # 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@v4 with: python-version: "3.9" - name: Install dependencies run: | python -m pip install --upgrade pip pip install wheel twine - name: Check the tag run: | PACKAGE_VERSION=`python setup.py --version` TAG_NAME=v$PACKAGE_VERSION echo "Package version $PACKAGE_VERSION with possible tag name $TAG_NAME on $GITHUB_REF_NAME" # test that the tag represents the version # see https://docs.gitlab.com/ee/ci/variables/predefined_variables.html # see https://docs.github.com/en/actions/learn-github-actions/environment-variables if [ "$TAG_NAME" != "$GITHUB_REF_NAME" ]; then echo "ERROR: This tag is for the wrong version. Got \"$GITHUB_REF_NAME\" expected \"$TAG_NAME\"." exit 1 fi - name: remove old files run: rm -rf dist/* - name: build distribution files run: python setup.py bdist_wheel sdist - name: deploy to pypi run: | # You will have to set the variables TWINE_USERNAME and TWINE_PASSWORD # You can use a token specific to your project by setting the user name to # __token__ and the password to the token given to you by the PyPI project. # sources: # - https://shambu2k.hashnode.dev/gitlab-to-pypi # - http://blog.octomy.org/2020/11/deploying-python-pacakges-to-pypi-using.html?m=1 # Also, set the tags as protected to allow the secrets to be used. # see https://docs.gitlab.com/ee/user/project/protected_tags.html 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/* x-wr-timezone-1.0.1/.gitignore000066400000000000000000000001341467641473200162420ustar00rootroot00000000000000/ENV __pycache__ *.pyc *egg-info /build /dist .pytest_cache/ .vscode/ ENV2 *.swp .tox .venv x-wr-timezone-1.0.1/.gitlab-ci.yml000066400000000000000000000112371467641473200167140ustar00rootroot00000000000000# This file is a template, and might need editing before it works on your project. # To contribute improvements to CI/CD templates, please follow the Development guide at: # https://docs.gitlab.com/ee/development/cicd/templates.html # This specific template is located at: # https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Python.gitlab-ci.yml # Official language image. Look for the different tagged releases at: # https://hub.docker.com/r/library/python/tags/ image: python:latest variables: # Change pip's cache directory to be inside the project directory since we can # only cache local items. PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" # Pip's cache doesn't store the python packages # https://pip.pypa.io/en/stable/reference/pip_install/#caching # # If you want to also cache the installed packages, you have to install # them in a virtualenv and cache it as well. cache: paths: - .cache/pip stages: - build - test - deploy build-package: stage: build script: - 'export PATH="$PATH:/root/.local/bin"' - python --version # For debugging # install from the zip file to see if files were forgotten - python setup.py sdist --dist-dir=dist --formats=zip - pip install --user --upgrade pip # download packages once and cache them for the others - pip install --user -r test-requirements.txt -r requirements.txt virtualenv wheel artifacts: paths: - sdist untracked: true .run-tests: needs: - build-package stage: test before_script: - 'export PATH="$PATH:/root/.local/bin"' - pip install --user --upgrade pip # install the package in a virtual env - pip install --user virtualenv - virtualenv ENV - source ENV/bin/activate # install package from build step - pip install dist/*.zip # test that the example works without the test dependencies - ./example.sh # install test requirements - pip install -r test-requirements.txt pytest-cov script: - test/test_code_quality.sh # using coverage see # - https://docs.gitlab.com/ee/user/project/merge_requests/test_coverage_visualization.html#python-example # - https://gitlab.com/gitlab-org/gitlab/-/issues/285086 - coverage run -m pytest --x-wr-timezone=all - coverage report - coverage xml artifacts: expire_in: 2 days reports: coverage_report: # format thanks to # https://stackoverflow.com/a/72138320 coverage_format: cobertura path: coverage.xml test-python-2.7: image: python:2.7 extends: - .run-tests test-python-3.7: image: python:3.7 extends: - .run-tests test-python-3.8: image: python:3.8 extends: - .run-tests test-python-3.9: image: python:3.9 extends: - .run-tests test-python-3.10: image: python:3.10 extends: - .run-tests deploy-package: # use python3.9 because piwheels.org build for this version image: python:3.9 stage: deploy needs: - test-python-2.7 - test-python-3.7 - test-python-3.8 - test-python-3.9 - test-python-3.10 before_script: - 'export PATH="$PATH:/root/.local/bin"' - pip install --user --upgrade pip # add piwheels so we can build on raspberry pi - pip config set global.extra-index-url 'https://www.piwheels.org/simple' - pip config list - pip install --user wheel twine script: - PACKAGE_VERSION=`python setup.py --version` - TAG_NAME=v$PACKAGE_VERSION - echo "Package version $PACKAGE_VERSION with possible tag name $TAG_NAME on $CI_COMMIT_TAG" # test that the tag represents the version # see https://docs.gitlab.com/ee/ci/variables/predefined_variables.html - '( if [ -n "$CI_COMMIT_TAG" ]; then if [ $TAG_NAME != $CI_COMMIT_TAG ]; then echo "This tag is for the wrong version. Got \"$CI_COMMIT_TAG\" expected \"$TAG_NAME\"."; exit 1; fi; fi; )' # remove old files - rm -rf dist/* # build new files - python setup.py bdist_wheel sdist # 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 # Also, set the tags as protected to allow the secrets to be used. # see https://docs.gitlab.com/ee/user/project/protected_tags.html - twine check dist/* - twine upload dist/* artifacts: paths: - dist/* only: # run only on tags with a certain name # see http://stackoverflow.com/questions/52830653/ddg#52859379 - /^v[0-9]+\.[0-9]+\.[0-9a-z]+/ x-wr-timezone-1.0.1/LICENSE000066400000000000000000000167441467641473200152750ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. x-wr-timezone-1.0.1/MANIFEST.in000066400000000000000000000000521467641473200160070ustar00rootroot00000000000000include *requirements.txt include LICENSE x-wr-timezone-1.0.1/README.rst000066400000000000000000000203211467641473200157410ustar00rootroot00000000000000X-WR-TIMEZONE ============= .. image:: https://github.com/niccokunzmann/x-wr-timezone/actions/workflows/tests.yml/badge.svg :target: https://github.com/niccokunzmann/x-wr-timezone/actions/workflows/tests.yml :alt: CI build and test status .. image:: https://badge.fury.io/py/x-wr-timezone.svg :target: https://pypi.python.org/pypi/x-wr-timezone :alt: Python Package Version on Pypi .. image:: https://img.shields.io/pypi/dm/x-wr-timezone.svg :target: https://pypi.python.org/pypi/x-wr-timezone#downloads :alt: Downloads from Pypi .. image:: https://img.shields.io/opencollective/all/open-web-calendar?label=support%20on%20open%20collective :target: https://opencollective.com/open-web-calendar/ :alt: Support on Open Collective Some calendar providers introduce the non-standard ``X-WR-TIMEZONE`` parameter to ICS calendar files. Strict interpretations according to RFC 5545 ignore the ``X-WR-TIMEZONE`` parameter. This causes the times of the events to differ from those which make use of ``X-WR-TIMEZONE``. This module aims to bridge the gap by converting calendars using ``X-WR-TIMEZONE`` to a strict RFC 5545 calendars. So, let's put our heads together and solve this problem for everyone! Some features of the module are: - Easy install with Python's ``pip``. - Command line conversion of calendars. - Piping of calendar files with ``wget`` or ``curl``. Some of the requirements are: - Calendars without ``X-WR-TIMEZONE`` are kept unchanged. - Passing calendars twice to this module does not change them. Install ------- Install using ``pip``: .. code:: shell python3 -m pip install x-wr-timezone Install with ``apt``: .. code:: shell sudo apt-get install python-x-wr-timezone Support ------- - `Donate using GitHub Sponsors `_ - `Donate using Open Collective `_ - `Donate using thanks.dev `_ Command Line Usage ------------------ You can standardize the calendars using your command line interface. The examples assume that ``in.ics`` is a calendar which may use ``X-WR-TIMEZONE``, whereas ``out.ics`` does not require ``X-WR-TIMEZONE`` for proper display. .. code-block:: shell cat in.is | x-wr-timezone > out.ics x-wr-timezone in.ics out.ics curl https://example.org/in.ics | x-wr-timezone > out.ics wget -O- https://example.org/in.ics | x-wr-timezone > out.ics You can get usage help on the command line: .. code-block:: shell x-wr-timezone --help Python ------ After you have installed the library, you can import it. .. code:: python import x_wr_timezone The function ``to_standard()`` converts an ``icalendar`` object. .. code:: python x_wr_timezone.to_standard(an_icalendar) Here is a full example which does about as much as this module is supposed to do: .. code-block:: python import icalendar # installed with x_wr_timezone import x_wr_timezone with open("in.ics", 'rb') as file: calendar = icalendar.from_ical(file.read()) new_calendar = x_wr_timezone.to_standard(calendar) # you could use the new_calendar variable now with open('out.ics', 'wb') as file: file.write(new_calendar.to_ical()) ``to_standard(calendar, timezone=None)`` has these parameters: - ``calendar`` is the ``icalendar.Calendar`` object. - ``timezone`` is an optional time zone. By default, the time zone in ``calendar['X-WR-TIMEZONE']`` is used to check if the calendar needs changing. When ``timezone`` is not ``None`` however, ``calendar['X-WR-TIMEZONE']`` will not be tested and it is assumed that the ``calendar`` should be changed as if ``calendar['X-WR-TIMEZONE']`` had the value of ``timezone``. This does not add or change the value of ``calendar['X-WR-TIMEZONE']``. You would need to do that yourself. ``timezone`` can be a string like ``"UTC"`` or ``"Europe/Berlin"`` or a ``pytz.timezone`` or something that ``datetime`` accepts as a time zone.. - Return value: The ``calendar`` argument is not modified at all. The calendar returned has the attributes and subcomponents of the ``calendar`` only changed and copied where needed to return the proper value. As such, the returned calendar might be identical to the one passed to the function as the ``calendar`` argument. Keep that in mind if you modify the return value. Development ----------- 1. Clone the `repository `_ or its fork and ``cd x-wr-timezone``. 2. Optional: Install virtualenv and Python3 and create a virtual environment: .. code-block:: shell pip install virtualenv virtualenv -p python3 ENV source ENV/bin/activate # you need to do this for each shell 3. Install the packages and this module so it can be edited: .. code-block:: shell pip install -r test-requirements.txt -e . 4. Run the tests: .. code-block:: shell pytest To test all functions: .. code-block:: shell pytest --x-wr-timezone all Testing with ``tox`` ******************** You can use ``tox`` to test the package in different Python versions. .. code-block:: shell tox This tests all the different functionalities: .. code-block:: shell tox -- --x-wr-timezone all New Releases ------------ To release new versions, 1. edit the Changelog Section 2. edit setup.py, the ``__version__`` variable 3. create a commit and push it 4. Wait for `CI tests `_ to finish the build. 5. run .. code-block:: shell python3 setup.py tag_and_deploy 6. notify the issues about their release Testing ******* This project's development is driven by tests. Tests assure a consistent interface and less knowledge lost over time. If you like to change the code, tests help that nothing breaks in the future. They are required in that sense. Example code and ics files can be transferred into tests and speed up fixing bugs. You can view the tests in the `test folder `_. If you have a calendar ICS file for which this library does not generate the desired output, you can add it to the ``test/calendars`` folder and write tests for what you expect. If you like, `open an issue `_ first, e.g. to discuss the changes and how to go about it. Changelog --------- - v1.0.0 - Use `zoneinfo` instead of `pytz` - Test compatibility with `pytz` and `zoneinfo` as argument to `to_standard` - Remove `pytz` as a dependency - Add `tzdata` as a dependency - Add typing - Update Python versions - v0.0.7 - Rename master branch to main - Use proper SPDX license ID - Test Python 3.12 - v0.0.6 - Obsolete Python 3.7 - Support Python 3.11 - Fix localization issue for pytz when datetime has no timezone - Run tests on GitHub Actions - Require icalendar 5.0.11 for tests - Fix pytz localization issue when dateime is not in UTC and has no time zone. - v0.0.5 - Revisit README and CLI and fix spelling mistakes. - Modified behavior to treat events without time zone found in a calendar using the X-WR-TIMEZONE property, see `Pull Request 7 `__ - v0.0.4 - Test automatic deployment with Gitlab CI. - v0.0.3 - Use ``tzname()`` function of ``datetime`` to test for UTC. This helps support zoneinfo time zones. - Split up visitor class and rename it to walker. - v0.0.2 - Implement the ``timezone`` argument. - Do not modify the value of the ``calendar`` argument and only copy it where needed. - v0.0.1 - Initial release supports DTSTART, DTEND, EXDATE, RDATE, RECURRENCE-ID attributes of events. - Command line interface as ``x-wr-timezone``. Related Work ------------ This module was reated beause of these issues: - `icalendar#343 `__ - `python-recurring-ical-events#71 `__ Related Software ---------------- This module uses the ``icalendar`` library for parsing calendars. This library is used by ``python-recurring-ical-events`` to get events at specific dates. License ------- This software is licensed under LGPLv3, see the LICENSE file. x-wr-timezone-1.0.1/example.sh000077500000000000000000000005611467641473200162500ustar00rootroot00000000000000#!/bin/bash cIn="test/calendars/x-wr-timezone-not-present.in.ics" cOut="test/calendars/x-wr-timezone-not-present.out.ics" cTest="/tmp/x-wr-timezone-not-present.out.ics" if ! x-wr-timezone "$cIn" "$cTest"; then echo "Error: x-wr-timezone did not run without error." exit 1 fi # Cannot compare calendars at this point, tests will do that. echo "Success!" exit 0 x-wr-timezone-1.0.1/requirements.txt000066400000000000000000000000211467641473200175310ustar00rootroot00000000000000icalendar tzdata x-wr-timezone-1.0.1/setup.py000066400000000000000000000125411467641473200157710ustar00rootroot00000000000000#!/usr/bin/python3 """The setup and build script for the library named "PACKAGE_NAME".""" import os import sys from setuptools.command.test import test as TestCommandBase from distutils.core import Command import subprocess PACKAGE_NAME = "x_wr_timezone" HERE = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, HERE) # for package import __version__ = "1.0.1" __author__ = 'Nicco Kunzmann' def read_file_named(file_name): file_path = os.path.join(HERE, file_name) with open(file_path) as file: return file.read() def read_requirements_file(file_name): content = read_file_named(file_name) lines = [] for line in content.splitlines(): comment_index = line.find("#") if comment_index >= 0: line = line[:comment_index] line = line.strip() if not line: continue lines.append(line) return lines # The base package metadata to be used by both distutils and setuptools METADATA = dict( name=PACKAGE_NAME, version=__version__, py_modules=[PACKAGE_NAME], author=__author__, author_email='niccokunzmann@rambler.ru', description='A Python module and program to convert calendars using X-WR-TIMEZONE to standard ones.', license='LGPL-3.0-or-later', url='https://github.com/niccokunzmann/x-wr-timezone', keywords='icalendar', entry_points="[console_scripts]\nx-wr-timezone = x_wr_timezone:main", ) # set development status from __version__ DEVELOPMENT_STATES = { "p": "Development Status :: 1 - Planning", "pa": "Development Status :: 2 - Pre-Alpha", "a": "Development Status :: 3 - Alpha", "b": "Development Status :: 4 - Beta", "": "Development Status :: 5 - Production/Stable", "m": "Development Status :: 6 - Mature", "i": "Development Status :: 7 - Inactive" } development_state = DEVELOPMENT_STATES[""] for ending in DEVELOPMENT_STATES: if ending and __version__.endswith(ending): development_state = DEVELOPMENT_STATES[ending] if not __version__[-1:].isdigit(): METADATA["version"] += "0" # tag and upload to repository to trigger auto deploy for tags class TagAndDeployCommand(Command): description = "Create a git tag for this version and push it to origin."\ "To trigger a CI build and and deploy." user_options = [] name = "tag_and_deploy" remote = "origin" branch = "main" def initialize_options(self): pass def finalize_options(self): pass def run(self): if subprocess.call(["git", "--version"]) != 0: print("ERROR:\n\tPlease install git.") exit(1) status_lines = subprocess.check_output(["git", "status"]).splitlines() current_branch = status_lines[0].strip().split()[-1].decode() print("On branch {}.".format(current_branch)) if current_branch != self.branch: print("ERROR:\n\tNew tags can only be made from branch \"{}\"." "".format(self.branch)) print("\tYou can use \"git checkout {}\" to switch the branch." "".format(self.branch)) exit(1) tags_output = subprocess.check_output(["git", "tag"]) tags = [tag.strip().decode() for tag in tags_output.splitlines()] tag = "v" + METADATA["version"] if tag in tags: print("Warning: \n\tTag {} already exists.".format(tag)) print("\tEdit the version information in {}".format( os.path.join(HERE, PACKAGE_NAME, "__init__.py") )) else: print("Creating tag \"{}\".".format(tag)) subprocess.check_call(["git", "tag", tag]) print("Pushing tag \"{}\" to remote \"{}\".".format(tag, self.remote)) subprocess.check_call(["git", "push", self.remote, tag]) # Extra package metadata to be used only if setuptools is installed required_packages = read_requirements_file("requirements.txt") required_test_packages = read_requirements_file("test-requirements.txt") SETUPTOOLS_METADATA = dict( install_requires=required_packages, tests_require=required_test_packages, include_package_data=False, classifiers=[ # https://pypi.python.org/pypi?%3Aaction=list_classifiers 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Utilities', 'Intended Audience :: Developers', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', development_state ], zip_safe=True, cmdclass={ TagAndDeployCommand.name: TagAndDeployCommand }, ) def main(): # Build the long_description from the README and CHANGES METADATA['long_description'] = read_file_named("README.rst") # Use setuptools if available, otherwise fallback and use distutils try: import setuptools METADATA.update(SETUPTOOLS_METADATA) setuptools.setup(**METADATA) except ImportError: import distutils.core distutils.core.setup(**METADATA) if __name__ == '__main__': main() x-wr-timezone-1.0.1/test-requirements.txt000066400000000000000000000000751467641473200205170ustar00rootroot00000000000000pytest restructuredtext-lint pygments icalendar>=5.0.11 pytz x-wr-timezone-1.0.1/test/000077500000000000000000000000001467641473200152335ustar00rootroot00000000000000x-wr-timezone-1.0.1/test/calendars/000077500000000000000000000000001467641473200171675ustar00rootroot00000000000000x-wr-timezone-1.0.1/test/calendars/Germany-Holidays-date-as-value-type.in.ics000066400000000000000000000333721467641473200270650ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Calendar Labs//Calendar 1.0//EN CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:Germany Holidays X-WR-TIMEZONE:Etc/GMT BEGIN:VEVENT SUMMARY:New Year's Day DTSTART:20190101 DTEND:20190101 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312427a1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Epiphany DTSTART:20190106 DTEND:20190106 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/spain/epiphany.php to know more about Epiphany. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31242de1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Carnival DTSTART:20190305 DTEND:20190305 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/brazil/carnival.php to know more about Carnival. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312433a1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Good Friday DTSTART:20190419 DTEND:20190419 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/good-friday.php to know more about Good Friday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31243901580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Easter Sunday DTSTART:20190421 DTEND:20190421 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/easter.php to know more about Easter Sunday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31243e91580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Easter Monday DTSTART:20190422 DTEND:20190422 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/christian/easter-monday.php to know more about Easter Monday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31244431580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Labor Day DTSTART:20190501 DTEND:20190501 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/uk/may-day.php to know more about Labor Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312449c1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Ascension Day DTSTART:20190530 DTEND:20190530 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/romania/ascension-day.php to know more about Ascension Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31244f81580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Whit Monday DTSTART:20190610 DTEND:20190610 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/pentecost-monday.php to know more about Whit Monday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31245501580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Corpus Christi DTSTART:20190620 DTEND:20190620 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/corpus-christi.php to know more about Corpus Christi. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31245aa1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Assumption Day DTSTART:20190815 DTEND:20190815 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/the-assumption-of-mary.php to know more about Assumption Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312460b1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Unity Day (National) DTSTART:20191003 DTEND:20191003 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/unity-day.php to know more about Unity Day (National). Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31246691580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Reformation Day DTSTART:20191031 DTEND:20191031 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/reformation-day.php to know more about Reformation Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31246e21580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:All Saints Day DTSTART:20191101 DTEND:20191101 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/philippines/all-saints-day.php to know more about All Saints Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31247411580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:National Day of Mourning DTSTART:20191117 DTEND:20191117 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/national-day-of-mourning.php to know more about National Day of Mourning. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31247a21580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Christmas Day DTSTART:20191225 DTEND:20191225 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/christmas.php to know more about Christmas Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31248001580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Boxing Day DTSTART:20191226 DTEND:20191226 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/canada/boxing-day.php to know more about Boxing Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312485a1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:New Year's Day DTSTART:20200101 DTEND:20200101 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31248b51580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Epiphany (BW\, BY & ST) DTSTART:20200106 DTEND:20200106 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/spain/epiphany.php to know more about Epiphany (BW\, BY & ST). Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312491a1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Carnival DTSTART:20200225 DTEND:20200225 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/brazil/carnival.php to know more about Carnival. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312497e1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Good Friday DTSTART:20200410 DTEND:20200410 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/good-friday.php to know more about Good Friday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31249e41580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Easter Sunday DTSTART:20200412 DTEND:20200412 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/easter.php to know more about Easter Sunday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124a431580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Easter Monday DTSTART:20200413 DTEND:20200413 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/christian/easter-monday.php to know more about Easter Monday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124aa51580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Labor Day DTSTART:20200501 DTEND:20200501 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/uk/may-day.php to know more about Labor Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124b021580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Ascension Day DTSTART:20200521 DTEND:20200521 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/romania/ascension-day.php to know more about Ascension Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124b5f1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Whit Monday DTSTART:20200601 DTEND:20200601 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/pentecost-monday.php to know more about Whit Monday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124bc41580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Corpus Christi DTSTART:20200611 DTEND:20200611 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/corpus-christi.php to know more about Corpus Christi. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124c291580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Assumption Day DTSTART:20200815 DTEND:20200815 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/the-assumption-of-mary.php to know more about Assumption Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124c891580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Unity Day (National) DTSTART:20201003 DTEND:20201003 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/unity-day.php to know more about Unity Day (National). Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124ceb1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Reformation Day DTSTART:20201031 DTEND:20201031 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/reformation-day.php to know more about Reformation Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124d521580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:All Saints Day DTSTART:20201101 DTEND:20201101 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/philippines/all-saints-day.php to know more about All Saints Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124dbe1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:National Day of Mourning DTSTART:20201115 DTEND:20201115 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/national-day-of-mourning.php to know more about National Day of Mourning. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124e231580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Christmas Day DTSTART:20201225 DTEND:20201225 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/christmas.php to know more about Christmas Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124e841580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Boxing Day DTSTART:20201226 DTEND:20201226 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/canada/boxing-day.php to know more about Boxing Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124eeb1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT END:VCALENDARx-wr-timezone-1.0.1/test/calendars/Germany-Holidays-date-as-value-type.out.ics000066400000000000000000000333721467641473200272660ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Calendar Labs//Calendar 1.0//EN CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:Germany Holidays X-WR-TIMEZONE:Etc/GMT BEGIN:VEVENT SUMMARY:New Year's Day DTSTART:20190101 DTEND:20190101 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312427a1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Epiphany DTSTART:20190106 DTEND:20190106 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/spain/epiphany.php to know more about Epiphany. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31242de1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Carnival DTSTART:20190305 DTEND:20190305 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/brazil/carnival.php to know more about Carnival. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312433a1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Good Friday DTSTART:20190419 DTEND:20190419 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/good-friday.php to know more about Good Friday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31243901580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Easter Sunday DTSTART:20190421 DTEND:20190421 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/easter.php to know more about Easter Sunday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31243e91580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Easter Monday DTSTART:20190422 DTEND:20190422 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/christian/easter-monday.php to know more about Easter Monday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31244431580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Labor Day DTSTART:20190501 DTEND:20190501 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/uk/may-day.php to know more about Labor Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312449c1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Ascension Day DTSTART:20190530 DTEND:20190530 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/romania/ascension-day.php to know more about Ascension Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31244f81580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Whit Monday DTSTART:20190610 DTEND:20190610 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/pentecost-monday.php to know more about Whit Monday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31245501580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Corpus Christi DTSTART:20190620 DTEND:20190620 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/corpus-christi.php to know more about Corpus Christi. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31245aa1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Assumption Day DTSTART:20190815 DTEND:20190815 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/the-assumption-of-mary.php to know more about Assumption Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312460b1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Unity Day (National) DTSTART:20191003 DTEND:20191003 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/unity-day.php to know more about Unity Day (National). Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31246691580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Reformation Day DTSTART:20191031 DTEND:20191031 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/reformation-day.php to know more about Reformation Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31246e21580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:All Saints Day DTSTART:20191101 DTEND:20191101 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/philippines/all-saints-day.php to know more about All Saints Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31247411580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:National Day of Mourning DTSTART:20191117 DTEND:20191117 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/national-day-of-mourning.php to know more about National Day of Mourning. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31247a21580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Christmas Day DTSTART:20191225 DTEND:20191225 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/christmas.php to know more about Christmas Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31248001580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Boxing Day DTSTART:20191226 DTEND:20191226 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/canada/boxing-day.php to know more about Boxing Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312485a1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:New Year's Day DTSTART:20200101 DTEND:20200101 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/new-years-day.php to know more about New Year's Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31248b51580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Epiphany (BW\, BY & ST) DTSTART:20200106 DTEND:20200106 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/spain/epiphany.php to know more about Epiphany (BW\, BY & ST). Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312491a1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Carnival DTSTART:20200225 DTEND:20200225 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/brazil/carnival.php to know more about Carnival. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f312497e1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Good Friday DTSTART:20200410 DTEND:20200410 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/good-friday.php to know more about Good Friday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f31249e41580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Easter Sunday DTSTART:20200412 DTEND:20200412 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/easter.php to know more about Easter Sunday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124a431580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Easter Monday DTSTART:20200413 DTEND:20200413 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/christian/easter-monday.php to know more about Easter Monday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124aa51580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Labor Day DTSTART:20200501 DTEND:20200501 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/uk/may-day.php to know more about Labor Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124b021580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Ascension Day DTSTART:20200521 DTEND:20200521 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/romania/ascension-day.php to know more about Ascension Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124b5f1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Whit Monday DTSTART:20200601 DTEND:20200601 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/pentecost-monday.php to know more about Whit Monday. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124bc41580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Corpus Christi DTSTART:20200611 DTEND:20200611 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/corpus-christi.php to know more about Corpus Christi. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124c291580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Assumption Day DTSTART:20200815 DTEND:20200815 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/the-assumption-of-mary.php to know more about Assumption Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124c891580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Unity Day (National) DTSTART:20201003 DTEND:20201003 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/unity-day.php to know more about Unity Day (National). Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124ceb1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Reformation Day DTSTART:20201031 DTEND:20201031 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/reformation-day.php to know more about Reformation Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124d521580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:All Saints Day DTSTART:20201101 DTEND:20201101 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/philippines/all-saints-day.php to know more about All Saints Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124dbe1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:National Day of Mourning DTSTART:20201115 DTEND:20201115 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/germany/national-day-of-mourning.php to know more about National Day of Mourning. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124e231580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Christmas Day DTSTART:20201225 DTEND:20201225 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/us/christmas.php to know more about Christmas Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124e841580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT BEGIN:VEVENT SUMMARY:Boxing Day DTSTART:20201226 DTEND:20201226 LOCATION:Germany DESCRIPTION:Visit https://calendarlabs.com/holidays/canada/boxing-day.php to know more about Boxing Day. Like us on Facebook: http://fb.com/calendarlabs to get updates RRULE: UID:5e3a8f3124eeb1580896049@calendarlabs.com DTSTAMP:20200205T094729Z STATUS:CONFIRMED TRANSP:TRANSPARENT SEQUENCE:0 END:VEVENT END:VCALENDARx-wr-timezone-1.0.1/test/calendars/exdate-hackerpublicradio-modified.in.ics000066400000000000000000000015231467641473200267760ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:Data::ICal 0.20 X-WR-CALNAME:Hacker Public Radio X-WR-TIMEZONE:Europe/London BEGIN:VEVENT DESCRIPTION:This is from http://www.hackerpublicradio.org/eps/hpr1286/iCalendar_ Hacking_shownotes.html DTEND:20130803T210000Z DTSTART:20130803T190000Z LOCATION:mumble.openspeak.cc port: 64747 EXDATE;VALUE=DATE-TIME:20130803T190000Z EXDATE;VALUE=DATE-TIME:20130831T190000Z EXDATE;VALUE=DATE-TIME:20131005T190000Z EXDATE;VALUE=DATE-TIME:20131102T190000Z EXDATE;VALUE=DATE-TIME:20131130T190000Z EXDATE;VALUE=DATE-TIME:20140104T190000Z EXDATE;VALUE=DATE-TIME:20140201T190000Z EXDATE;VALUE=DATE-TIME:20140301T190000Z EXDATE;VALUE=DATE-TIME:20140405T190000Z EXDATE;VALUE=DATE-TIME:20140503T190000Z EXDATE;VALUE=DATE-TIME:20140531T190000Z EXDATE;VALUE=DATE-TIME:20140705T190000Z SUMMARY:HPR Community News END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/calendars/exdate-hackerpublicradio-modified.out.ics000066400000000000000000000021201467641473200271710ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:Data::ICal 0.20 X-WR-CALNAME:Hacker Public Radio X-WR-TIMEZONE:Europe/London BEGIN:VEVENT DESCRIPTION:This is from http://www.hackerpublicradio.org/eps/hpr1286/iCalendar_ Hacking_shownotes.html DTEND;TZID=Europe/London:20130803T220000 DTSTART;TZID=Europe/London:20130803T200000Z LOCATION:mumble.openspeak.cc port: 64747 EXDATE;TZID=Europe/London;VALUE=DATE-TIME:20130803T200000 EXDATE;TZID=Europe/London;VALUE=DATE-TIME:20130831T200000 EXDATE;TZID=Europe/London;VALUE=DATE-TIME:20131005T200000 EXDATE;TZID=Europe/London;VALUE=DATE-TIME:20131102T190000 EXDATE;TZID=Europe/London;VALUE=DATE-TIME:20131130T190000 EXDATE;TZID=Europe/London;VALUE=DATE-TIME:20140104T190000 EXDATE;TZID=Europe/London;VALUE=DATE-TIME:20140201T190000 EXDATE;TZID=Europe/London;VALUE=DATE-TIME:20140301T190000 EXDATE;TZID=Europe/London;VALUE=DATE-TIME:20140405T200000 EXDATE;TZID=Europe/London;VALUE=DATE-TIME:20140503T200000 EXDATE;TZID=Europe/London;VALUE=DATE-TIME:20140531T200000 EXDATE;TZID=Europe/London;VALUE=DATE-TIME:20140705T200000 SUMMARY:HPR Community News END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/calendars/moved-event-RECURRENCE-ID.in.ics000066400000000000000000000031631467641473200244550ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:Partyborn Zeitgeist X-WR-TIMEZONE:Europe/Berlin X-WR-CALDESC:Alle Events des Zeitgeist Paderborn für den Partyborn Partyala rm\nhttps://partyborn.de/partyalarm BEGIN:VTIMEZONE TZID:Europe/Berlin X-LIC-LOCATION:Europe/Berlin 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/Berlin:20211217T213000 DTSTAMP:20211218T004508Z UID:38m812jicsrer5gorh3mlp7qhc@google.com RECURRENCE-ID:20211231T203000Z CREATED:20211218T004036Z DESCRIPTION:Jeden letzten Freitag im Monat https://www.instagram.com/p/CWwxmqbKXsC/ LAST-MODIFIED:20211218T004234Z LOCATION: SEQUENCE:3 STATUS:CONFIRMED SUMMARY:Karaoke TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT X-COMMENT:This is not the original. It was changed because I could not find a calendar that had a RECURRENCE-ID in UTC. DTSTART:20211126T203000Z DTEND:20211126T203000Z RRULE:FREQ=MONTHLY;BYDAY=-1FR DTSTAMP:20211218T004508Z UID:38m812jicsrer5gorh3mlp7qhc@google.com CREATED:20211218T004036Z DESCRIPTION:Jeden letzten Freitag im Monat https://www.instagram.com/p/CWwxmqbKXsC/ LAST-MODIFIED:20211218T004214Z LOCATION: SEQUENCE:2 STATUS:CONFIRMED SUMMARY:Karaoke TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/calendars/moved-event-RECURRENCE-ID.out.ics000066400000000000000000000032511467641473200246540ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:Partyborn Zeitgeist X-WR-TIMEZONE:Europe/Berlin X-WR-CALDESC:Alle Events des Zeitgeist Paderborn für den Partyborn Partyala rm\nhttps://partyborn.de/partyalarm BEGIN:VTIMEZONE TZID:Europe/Berlin X-LIC-LOCATION:Europe/Berlin 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/Berlin:20211217T213000 DTSTAMP:20211218T004508Z UID:38m812jicsrer5gorh3mlp7qhc@google.com RECURRENCE-ID;TZID=Europe/Berlin:20211231T213000 CREATED:20211218T004036Z DESCRIPTION:Jeden letzten Freitag im Monat https://www.instagram.com/p/CWwxmqbKXsC/ LAST-MODIFIED:20211218T004234Z LOCATION: SEQUENCE:3 STATUS:CONFIRMED SUMMARY:Karaoke TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT X-COMMENT:This is not the original. It was changed because I could not find a calendar that had a RECURRENCE-ID in UTC. DTSTART;TZID=Europe/Berlin:20211126T213000 DTEND;TZID=Europe/Berlin:20211126T213000 RRULE:FREQ=MONTHLY;BYDAY=-1FR DTSTAMP:20211218T004508Z UID:38m812jicsrer5gorh3mlp7qhc@google.com CREATED:20211218T004036Z DESCRIPTION:Jeden letzten Freitag im Monat https://www.instagram.com/p/CWwxmqbKXsC/ LAST-MODIFIED:20211218T004214Z LOCATION: SEQUENCE:2 STATUS:CONFIRMED SUMMARY:Karaoke TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/calendars/rdate-hackerpublicradio.in.ics000066400000000000000000000015071467641473200250470ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:Data::ICal 0.20 X-WR-CALNAME:Hacker Public Radio X-WR-TIMEZONE:Europe/London BEGIN:VEVENT DESCRIPTION:This is from http://www.hackerpublicradio.org/eps/hpr1286/iCalendar_ Hacking_shownotes.html DTEND:20130803T210000Z DTSTART:20130803T190000Z LOCATION:mumble.openspeak.cc port: 64747 RDATE;VALUE=DATE-TIME:20130803T190000Z RDATE;VALUE=DATE-TIME:20130831T190000Z RDATE;VALUE=DATE-TIME:20131005T190000Z RDATE;VALUE=DATE-TIME:20131102T190000Z RDATE;VALUE=DATE-TIME:20131130T190000Z RDATE;VALUE=DATE-TIME:20140104T190000Z RDATE;VALUE=DATE-TIME:20140201T190000Z RDATE;VALUE=DATE-TIME:20140301T190000Z RDATE;VALUE=DATE-TIME:20140405T190000Z RDATE;VALUE=DATE-TIME:20140503T190000Z RDATE;VALUE=DATE-TIME:20140531T190000Z RDATE;VALUE=DATE-TIME:20140705T190000Z SUMMARY:HPR Community News END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/calendars/rdate-hackerpublicradio.out.ics000066400000000000000000000021041467641473200252420ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:Data::ICal 0.20 X-WR-CALNAME:Hacker Public Radio X-WR-TIMEZONE:Europe/London BEGIN:VEVENT DESCRIPTION:This is from http://www.hackerpublicradio.org/eps/hpr1286/iCalendar_ Hacking_shownotes.html DTEND;TZID=Europe/London:20130803T220000 DTSTART;TZID=Europe/London:20130803T200000Z LOCATION:mumble.openspeak.cc port: 64747 RDATE;TZID=Europe/London;VALUE=DATE-TIME:20130803T200000 RDATE;TZID=Europe/London;VALUE=DATE-TIME:20130831T200000 RDATE;TZID=Europe/London;VALUE=DATE-TIME:20131005T200000 RDATE;TZID=Europe/London;VALUE=DATE-TIME:20131102T190000 RDATE;TZID=Europe/London;VALUE=DATE-TIME:20131130T190000 RDATE;TZID=Europe/London;VALUE=DATE-TIME:20140104T190000 RDATE;TZID=Europe/London;VALUE=DATE-TIME:20140201T190000 RDATE;TZID=Europe/London;VALUE=DATE-TIME:20140301T190000 RDATE;TZID=Europe/London;VALUE=DATE-TIME:20140405T200000 RDATE;TZID=Europe/London;VALUE=DATE-TIME:20140503T200000 RDATE;TZID=Europe/London;VALUE=DATE-TIME:20140531T200000 RDATE;TZID=Europe/London;VALUE=DATE-TIME:20140705T200000 SUMMARY:HPR Community News END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/calendars/single-event-no-tz.in.ics000066400000000000000000000007121467641473200237410ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:manually_generated X-WR-TIMEZONE:Europe/Brussels X-WR-CALNAME:MyCalendar X-WR-CALDESC:Description of calendar BEGIN:VEVENT UID:match_1025179 DTSTAMP:20210911T014015Z DESCRIPTION:When X-WR-TIMEZONE is ignored, this is a floating event, when it is interpreted, the event is assumed to be in the Europe/Brussels timezone DTSTART:20210916T210000 DTEND:20210916T224500 SUMMARY:X-WR-TIMEZONE no timezone END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/calendars/single-event-no-tz.out.ics000066400000000000000000000007641467641473200241510ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:manually_generated X-WR-TIMEZONE:Europe/Brussels X-WR-CALNAME:MyCalendar X-WR-CALDESC:Description of calendar BEGIN:VEVENT UID:match_1025179 DTSTAMP:20210911T014015Z DESCRIPTION:When X-WR-TIMEZONE is ignored, this is a floating event, when it is interpreted, the event is assumed to be in the Europe/Brussels timezone DTSTART;TZID=Europe/Brussels:20210916T210000 DTEND;TZID=Europe/Brussels:20210916T224500 SUMMARY:X-WR-TIMEZONE no timezone END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/calendars/single-event-x-wr-timezone-not-used.in.ics000066400000000000000000000011651467641473200271560ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:This calendar uses X-WR-TIMEZONE but all events are already in the expected time zone. X-WR-TIMEZONE:America/New_York BEGIN:VEVENT DTSTART;TZID=America/New_York:20211222T170000Z DTEND;TZID=America/New_York:20211222T180000Z DTSTAMP:20211228T183100Z UID:2tsd6j1s661b7pvlcuopnoa2fe@google.com CREATED:20211228T183040Z DESCRIPTION: LAST-MODIFIED:20211228T183040Z LOCATION: SEQUENCE:0 STATUS:CONFIRMED SUMMARY:Google recognizes this as EST 5PM-6PM TRANSP:OPAQUE END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/calendars/single-event-x-wr-timezone-not-used.out.ics000066400000000000000000000011661467641473200273600ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:This calendar uses X-WR-TIMEZONE but all events are already i n the expected time zone. X-WR-TIMEZONE:America/New_York BEGIN:VEVENT DTSTART;TZID=America/New_York:20211222T170000 DTEND;TZID=America/New_York:20211222T180000 DTSTAMP:20211228T183100Z UID:2tsd6j1s661b7pvlcuopnoa2fe@google.com CREATED:20211228T183040Z DESCRIPTION: LAST-MODIFIED:20211228T183040Z LOCATION: SEQUENCE:0 STATUS:CONFIRMED SUMMARY:Google recognizes this as EST 5PM-6PM TRANSP:OPAQUE END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/calendars/single-events-DTSTART-DTEND.in.ics000066400000000000000000000016311467641473200250370ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:This calendar features two events of which DTSTART and DTEND must be changed. X-WR-TIMEZONE:America/New_York BEGIN:VEVENT DTSTART:20211222T170000Z DTEND:20211222T180000Z DTSTAMP:20211228T180046Z UID:3bc4jff97631or97ntnk75n4se@google.com CREATED:20211222T190737Z DESCRIPTION: LAST-MODIFIED:20211222T190947Z LOCATION: SEQUENCE:2 STATUS:CONFIRMED SUMMARY:Google Calendar says this is noon to 1PM on 12/22/2021 TRANSP:OPAQUE END:VEVENT BEGIN:VEVENT DTSTART:20211223T020000Z DTEND:20211223T030000Z DTSTAMP:20211228T180046Z UID:14n7h56i35m32ukcq76s46d45p@google.com CREATED:20211222T190622Z DESCRIPTION: LAST-MODIFIED:20211222T190622Z LOCATION: SEQUENCE:0 STATUS:CONFIRMED SUMMARY:Google says this is 9PM to 10PM on 12/22/2021 TRANSP:OPAQUE END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/calendars/single-events-DTSTART-DTEND.out.ics000066400000000000000000000017601467641473200252430ustar00rootroot00000000000000BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:This calendar features two events of which DTSTART and DTEND must be changed. X-WR-TIMEZONE:America/New_York BEGIN:VEVENT DTSTART;TZID=America/New_York:20211222T120000 DTEND;TZID=America/New_York:20211222T130000 DTSTAMP:20211228T180046Z UID:3bc4jff97631or97ntnk75n4se@google.com CREATED:20211222T190737Z DESCRIPTION: LAST-MODIFIED:20211222T190947Z LOCATION: SEQUENCE:2 STATUS:CONFIRMED SUMMARY:Google Calendar says this is noon to 1PM on 12/22/2021 TRANSP:OPAQUE END:VEVENT BEGIN:VEVENT DTSTART;TZID=America/New_York:20211222T210000 DTEND;TZID=America/New_York:20211222T220000 DTSTAMP:20211228T180046Z UID:14n7h56i35m32ukcq76s46d45p@google.com CREATED:20211222T190622Z DESCRIPTION: LAST-MODIFIED:20211222T190622Z LOCATION: SEQUENCE:0 STATUS:CONFIRMED SUMMARY:Google says this is 9PM to 10PM on 12/22/2021 TRANSP:OPAQUE END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/calendars/x-wr-timezone-not-present.in.ics000066400000000000000000000013671467641473200253040ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:-//SabreDAV//SabreDAV//EN CALSCALE:GREGORIAN X-WR-CALNAME:test X-APPLE-CALENDAR-COLOR:#e78074 BEGIN:VTIMEZONE TZID:Europe/Berlin X-LIC-LOCATION:Europe/Berlin 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 CREATED:20190303T111937 DTSTAMP:20190303T111937 LAST-MODIFIED:20190303T111937 UID:UYDQSG9TH4DE0WM3QFL2J SUMMARY:test1 DTSTART;TZID=Europe/Berlin:20190304T080000 DTEND;TZID=Europe/Berlin:20190304T083000 END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/calendars/x-wr-timezone-not-present.out.ics000066400000000000000000000013671467641473200255050ustar00rootroot00000000000000BEGIN:VCALENDAR VERSION:2.0 PRODID:-//SabreDAV//SabreDAV//EN CALSCALE:GREGORIAN X-WR-CALNAME:test X-APPLE-CALENDAR-COLOR:#e78074 BEGIN:VTIMEZONE TZID:Europe/Berlin X-LIC-LOCATION:Europe/Berlin 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 CREATED:20190303T111937 DTSTAMP:20190303T111937 LAST-MODIFIED:20190303T111937 UID:UYDQSG9TH4DE0WM3QFL2J SUMMARY:test1 DTSTART;TZID=Europe/Berlin:20190304T080000 DTEND;TZID=Europe/Berlin:20190304T083000 END:VEVENT END:VCALENDAR x-wr-timezone-1.0.1/test/conftest.py000066400000000000000000000133351467641473200174370ustar00rootroot00000000000000"""Test and fixture initialization.""" import icalendar import pytest import sys import os import tempfile import shutil import subprocess HERE = os.path.dirname(__file__) or "." REPO = os.path.join(HERE, "..") sys.path.append(REPO) import x_wr_timezone CALENDARS_FOLDER = os.path.join(HERE, "calendars") NAME_END_INPUT = ".in" NAME_END_OUTPUT = ".out" EXECUTABLE = 'x-wr-timezone' example_calendars = {} # name: calendar class TestCalendar: """A calendar in the test folder.""" def __init__(self, path): self.path = path @property def filename(self): return os.path.basename(self.path) @property def filename_without_ending(self): return os.path.splitext(self.filename)[0] @property def name(self): """Return the nicely readable id of the calendar.""" return self.filename_without_ending.replace("-", " ") def is_input(self): """Whether this is a calendar which is used as an input for to_standard().""" return self.name.endswith(NAME_END_INPUT) def is_corrected_output(self): """Whether this is a calendar which would be a result of to_standard().""" return self.name.endswith(NAME_END_OUTPUT) def get_corrected_output_name(self): assert self.is_input(), "Only input calendars can have an output." return self.name[:len(self.name) - len(NAME_END_INPUT)] + NAME_END_OUTPUT def has_corrected_output(self): """Whether this has a calendar resulting from to_standard().""" return self.get_corrected_output_name() in example_calendars def get_corrected_output(self): """Return the corrected output of to_standard() for this calendar. KeyError if absent.""" return example_calendars[self.get_corrected_output_name()] def __repr__(self): return "<{}>".format(self.name) def as_bytes(self): with open(self.path, "rb") as file: return file.read() def as_icalendar(self): return icalendar.Calendar.from_ical(self.as_bytes()) def to_ical(self): return self.as_icalendar().to_ical() for calendar_file in os.listdir(CALENDARS_FOLDER): calendar_path = os.path.join(CALENDARS_FOLDER, calendar_file) calendar = TestCalendar(calendar_path) example_calendars[calendar.name] = calendar example_calendars[calendar.filename] = calendar example_calendars[calendar.filename_without_ending] = calendar class CalendarPair: """A pair of input and output calendars.""" def __init__(self, input, output): self.input = input self.output = output @property def message(self): return self.input.as_icalendar().get("X-WR-CALNAME", "Pair of {} + {}".format(self.input.name, NAME_END_OUTPUT)) def __repr__(self): return "<{}+{}>".format(self.input.name, NAME_END_OUTPUT) # for parametrizing fixtures, see https://docs.pytest.org/en/latest/fixture.html#parametrizing-fixtures @pytest.fixture(params=[calendar for calendar in example_calendars.values() if calendar.is_input() and calendar.has_corrected_output()]) def calendar_pair(request): """A pair of input and output calendar examples from the test/calendars folder.""" return CalendarPair(request.param, request.param.get_corrected_output()) @pytest.fixture(params=[calendar for calendar in example_calendars.values() if calendar.is_corrected_output()]) def output_calendar(request): """A TestCalendar ending with .out in from the test/calendars folder.""" return request.param def to_standard_cmd_stdio(calendar): """Use the command line and piping.""" input = calendar.to_ical() process = subprocess.Popen([EXECUTABLE], stdout=subprocess.PIPE, stdin=subprocess.PIPE) output = process.communicate(input)[0] assert process.returncode == 0, "The process should not error." return icalendar.Calendar.from_ical(output) def to_standard_cmd_file(calendar): d = tempfile.mkdtemp(prefix="pytest-") try: input = calendar.to_ical() in_path = os.path.join(d, "in.ics") out_path = os.path.join(d, "out.ics") with open(in_path, 'wb') as f: f.write(input) subprocess.check_call([EXECUTABLE, in_path, out_path]) with open(out_path, 'rb') as f: output = f.read() finally: shutil.rmtree(d) return icalendar.Calendar.from_ical(output) conversions = { "all": [x_wr_timezone.to_standard, to_standard_cmd_stdio, to_standard_cmd_file], "fast": [x_wr_timezone.to_standard], "io": [to_standard_cmd_stdio], "file": [to_standard_cmd_file], } @pytest.fixture(params=[ x_wr_timezone.to_standard, to_standard_cmd_stdio, to_standard_cmd_file, ]) def to_standard(request, pytestconfig): """Change the to_standard() function to test several different methods. Use: - fast - use x_wr_timezone.to_standard(...) - io - use cat ... > x-wr-timezone - file - use x-wr-timezone in.ics out.ics - all - all of the above """ to_standard = request.param if to_standard not in conversions[pytestconfig.option.to_standard]: pytest.skip("Use --x-wr-timezone=all to ativate all tests.") return to_standard def pytest_addoption(parser): group = parser.getgroup("x-wr-timezone") group.addoption( "--x-wr-timezone", action="store", dest="to_standard", choices=("all", "file", "io", "fast"), default="fast", metavar="MODE", help=to_standard.__doc__, ) @pytest.fixture() def calendars(): """A mapping of all TestCalendars in the test/calendars folder.""" return example_calendars.copy() @pytest.fixture() def todo(): """Skip a test because it needs to be written first.""" pytest.skip("This test is not yet implemented.") x-wr-timezone-1.0.1/test/test_code_quality.sh000077500000000000000000000005401467641473200213120ustar00rootroot00000000000000#!/bin/sh set -e cd "`dirname \"$0\"`" cd .. EXIT=0 if grep -n -H 'print(' x_wr_timezone.py; then echo "FAIL: print should not be used in production code." EXIT=1 fi cd test if grep -n -H -E '^[^#]*(today\(|now\()' *.py; then echo "FAIL: now() and today() must not be used in test code." EXIT=2 fi echo "exit $EXIT" exit "$EXIT" x-wr-timezone-1.0.1/test/test_command_line.py000066400000000000000000000003651467641473200212750ustar00rootroot00000000000000"""Test the command line interfae explicitely""" import subprocess CMD = "x-wr-timezone" def test_help(): """Test that a help is being displayed.""" help = subprocess.check_output([CMD, "--help"]) assert b'x-wr-timezone' in help x-wr-timezone-1.0.1/test/test_convert_examples.py000066400000000000000000000133021467641473200222210ustar00rootroot00000000000000"""This test converts example calendars""" import pytest import x_wr_timezone import zoneinfo import pytz def assert_has_line(bytes, content, message): lines = message_lines = [line for line in bytes.decode("UTF-8").splitlines() if content[0] in line] for c in content: lines = [line for line in lines if c in line] assert lines, message + " One of these lines should contain {}: {}".format(content, message_lines) def test_input_to_output(to_standard, calendar_pair): """Test the calendars which are a pair of input and output.""" output = to_standard(calendar_pair.input.as_icalendar()) print(output.walk("VEVENT")[0]["DTSTART"]) print(calendar_pair.output.as_icalendar().walk("VEVENT")[0]["DTSTART"]) assert output == calendar_pair.output.as_icalendar(), calendar_pair.message def test_output_stays_the_same(to_standard, output_calendar): assert output_calendar.is_corrected_output() output = to_standard(output_calendar.as_icalendar()) assert output == output_calendar.as_icalendar(), "A calendar that was modified one should stay as it is." def RDATE(dt): return ("rdate-hackerpublicradio.in.ics", ("RDATE", "TZID=Europe/London", dt), "RDATE is converted with value " + dt) def EXDATE(dt): return ("exdate-hackerpublicradio-modified.in.ics", ("EXDATE", "TZID=Europe/London", dt), "EXDATE is converted with value " + dt) @pytest.mark.parametrize("calendar_name,content,message", [ # DTSTART;TZID=America/New_York:20211222T120000 # DTEND;TZID=America/New_York:20211222T130000 ("single-events-DTSTART-DTEND.in.ics", ("DTSTART", "TZID=America/New_York", ":20211222T120000"), "DTSTART should be converted."), ("single-events-DTSTART-DTEND.in.ics", ("DTEND", "TZID=America/New_York", ":20211222T130000"), "DTEND should be converted."), ("single-events-DTSTART-DTEND.out.ics", ("DTSTART", "TZID=America/New_York", ":20211222T120000"), "DTSTART stays the same in already converted calendar."), # DTSTART;TZID=America/New_York:20211222T210000 # DTEND;TZID=America/New_York:20211222T220000 ("single-events-DTSTART-DTEND.in.ics", ("DTSTART", "TZID=America/New_York", ":20211222T210000"), "DTSTART should be converted."), ("single-events-DTSTART-DTEND.in.ics", ("DTEND", "TZID=America/New_York", ":20211222T220000"), "DTEND should be converted."), RDATE("20130803T200000"), # summer RDATE("20130831T200000"), # summer RDATE("20131005T200000"), # summer RDATE("20131102T190000"), RDATE("20131130T190000"), RDATE("20140104T190000"), RDATE("20140201T190000"), RDATE("20140301T190000"), RDATE("20140405T200000"), # summer RDATE("20140503T200000"), # summer RDATE("20140531T200000"), # summer RDATE("20140705T200000"), # summer # RECURRENCE-ID as well as DTSTART and DTEND ("moved-event-RECURRENCE-ID.in.ics", ("RECURRENCE-ID", "TZID=Europe/Berlin", "20211231T213000"), "The RECURRENCE-ID depends on DTSTART and should therefore be converted."), ("moved-event-RECURRENCE-ID.in.ics", ("DTSTART", "TZID=Europe/Berlin", "20211126T21"), "DTSTART is converted."), ("moved-event-RECURRENCE-ID.in.ics", ("DTEND", "TZID=Europe/Berlin", "20211126T213000"), "DTEND is converted."), # test EXDATE EXDATE("20130803T200000"), # summer EXDATE("20130831T200000"), # summer EXDATE("20131005T200000"), # summer EXDATE("20131102T190000"), EXDATE("20131130T190000"), EXDATE("20140104T190000"), EXDATE("20140201T190000"), EXDATE("20140301T190000"), EXDATE("20140405T200000"), # summer EXDATE("20140503T200000"), # summer EXDATE("20140531T200000"), # summer EXDATE("20140705T200000"), # summer ]) def test_conversion_changes_the_time_zone(to_standard, calendars, calendar_name, content, message): calendar = calendars[calendar_name] new_calendar = to_standard(calendar.as_icalendar()) output_bytes = new_calendar.to_ical() assert_has_line(output_bytes, content, message) @pytest.mark.parametrize("calendar_name,content,message", [ ("single-event-no-tz.in.ics", ("DTSTART", "TZID=Europe/Brussels", ":20210916T210000"), "DTSTART should be updated."), ("single-event-no-tz.in.ics", ("DTEND", "TZID=Europe/Brussels", ":20210916T224500"), "DTEND should be updated."), ("single-event-no-tz.out.ics", ("DTSTART", "TZID=Europe/Brussels", ":20210916T210000"), "DTSTART stays the same in already converted calendar."), ]) def test_conversion_adds_the_time_zone(to_standard, calendars, calendar_name, content, message): calendar = calendars[calendar_name] new_calendar = to_standard(calendar.as_icalendar()) output_bytes = new_calendar.to_ical() print(output_bytes) assert_has_line(output_bytes, content, message) @pytest.mark.parametrize("tz,line,message,calendar_name", [ ("Europe/Paris", ("DTSTART", "TZID=Europe/Paris" ,"20211223T030000"), "(1) Use string as timezone", "single-events-DTSTART-DTEND.in.ics"), (zoneinfo.ZoneInfo("Europe/Berlin"), ("DTSTART", "TZID=Europe/Berlin", "20211223T030000"), "(2) Use zoneinfo's timezone as timezone", "single-events-DTSTART-DTEND.in.ics"), (zoneinfo.ZoneInfo("UTC"), ("DTSTART", "20211222T170000Z"), "(3) Use zoninfo's UTC as timezone", "single-events-DTSTART-DTEND.in.ics"), (pytz.timezone("Europe/Berlin"), ("DTSTART", "TZID=Europe/Berlin", "20211223T030000"), "(4) Use pytz.timezone as timezone", "single-events-DTSTART-DTEND.in.ics"), (pytz.UTC, ("DTSTART", "20211222T170000Z"), "(5) Use pytz.UTC as timezone", "single-events-DTSTART-DTEND.in.ics"), ]) def test_timezone_parameter(calendars, tz, line, message, calendar_name): calendar = calendars[calendar_name] new_calendar = x_wr_timezone.to_standard(calendar.as_icalendar(), timezone=tz) output_bytes = new_calendar.to_ical() assert_has_line(output_bytes, line, message) x-wr-timezone-1.0.1/test/test_copy.py000066400000000000000000000046641467641473200176300ustar00rootroot00000000000000"""These tests check that the argument is not modified.""" from x_wr_timezone import to_standard import pytest @pytest.mark.parametrize("calendar_name,message", [ ("x-wr-timezone-not-present.in.ics", "No X-WR-TIMEZONE no change"), ("rdate-hackerpublicradio.out.ics", "no change, same calendar"), ]) def test_no_change(calendars, calendar_name, message): """Test when calendars do not change.""" calendar = calendars[calendar_name].as_icalendar() same_calendar = to_standard(calendar) assert same_calendar is calendar, message def test_calendar_is_changed(calendars): """If X-WR-TIMEZONE changes the calendar, it should create a copy.""" calendar = calendars["rdate-hackerpublicradio.in.ics"].as_icalendar() changed_calendar = to_standard(calendar) assert changed_calendar is not calendar def test_components_are_not_altered(calendars): """The components of the calendar should be altered but originals left intact.""" calendar = calendars["rdate-hackerpublicradio.in.ics"].as_icalendar() changed_calendar = to_standard(calendar) output_original = calendar.to_ical().decode("UTF-8") output_original_lines = output_original.splitlines() output_changed = changed_calendar.to_ical().decode("UTF-8") output_changed_lines = output_changed.splitlines() for original_line, new_line in zip(output_original_lines, output_changed_lines): print("o:\t", original_line, "\n\t", new_line) assert output_original_lines != output_changed_lines def get_lines(calendar): output_changed = calendar.to_ical().decode("UTF-8") return output_changed.splitlines() def filter_lines(calendar, content): return [line for line in get_lines(calendar) if content in line] @pytest.mark.parametrize("calendar_name", [ "rdate-hackerpublicradio.in.ics", "moved-event-RECURRENCE-ID.in.ics", ]) @pytest.mark.parametrize("property", "RDATE,BEGIN,BEGIN:CALENDAR,BEGIN:EVENT,END:EVENT,END:CALENDAR,DTSTART,DTEND,SUMMARY".split(",")) def test_components_all_in_there(calendars, calendar_name, property): """Make sure all components are there.""" calendar = calendars[calendar_name].as_icalendar() l1 = filter_lines(calendar, property) changed_calendar = to_standard(calendar) l2 = filter_lines(calendar, property) l3 = filter_lines(changed_calendar, property) assert l1 == l2, "calendar itself should not have changed" assert len(l2) == len(l3), "no components should be added" x-wr-timezone-1.0.1/test/test_examples.py000066400000000000000000000006501467641473200204630ustar00rootroot00000000000000"""Make sure the examples are what we want them to be""" def test_calendars_are_the_same(calendars): assert calendars["single-event-x-wr-timezone-not-used.in"].to_ical() == \ calendars["single-event-x-wr-timezone-not-used.out"].to_ical() def test_calendars_differ(calendars): assert calendars["single-events-DTSTART-DTEND.in"].to_ical() != \ calendars["single-events-DTSTART-DTEND.out"].to_ical() x-wr-timezone-1.0.1/test/test_readme.py000066400000000000000000000012371467641473200201040ustar00rootroot00000000000000''' Test the README file. This is necessary because a deployment does not work if the README file has errors. Credits: https://stackoverflow.com/a/47494076/1320237 ''' import os import sys import restructuredtext_lint HERE = os.path.dirname(__file__) readme_path = os.path.join(os.path.dirname(HERE), "README.rst") def test_readme_file(): '''CHeck README file for errors.''' messages = restructuredtext_lint.lint_file(readme_path) error_message = "expected to have no messages about the README file!" for message in messages: print(message.astext()) error_message += "\n" + message.astext() assert len(messages) == 0, error_messagex-wr-timezone-1.0.1/tox.ini000066400000000000000000000007631467641473200155750ustar00rootroot00000000000000# tox (https://tox.readthedocs.io/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py39, py310, py311, py312 requires = setuptools>=68.2.2 [testenv] setenv = TMPDIR={envtmpdir} deps = -r {toxinidir}/requirements.txt -r {toxinidir}/test-requirements.txt commands = pytest --basetemp="{envtmpdir}" {posargs} x-wr-timezone-1.0.1/x_wr_timezone.py000066400000000000000000000166141467641473200175270ustar00rootroot00000000000000# This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """Bring calendars using X-WR-TIMEZONE into RFC 5545 form.""" import sys import zoneinfo from icalendar.prop import vDDDTypes, vDDDLists import datetime import icalendar from typing import Optional X_WR_TIMEZONE = "X-WR-TIMEZONE" def list_is(l1, l2): """Return wether all contents of two lists are identical.""" return len(l1) == len(l2) and all(e1 is e2 for e1, e2 in zip(l1, l2)) class CalendarWalker: """I walk along the components and values of an icalendar object. The idea is the same as a visitor pattern. """ VALUE_ATTRIBUTES = ['DTSTART', 'DTEND', 'RDATE', 'RECURRENCE-ID', 'EXDATE'] def copy_if_changed(self, component, attributes, subcomponents): """Check if an icalendar Component has changed and copy it if it has. atributes and subcomponents are put into the copy.""" for key, value in attributes.items(): if component[key] is not value: return self.copy_component(component, attributes, subcomponents) assert len(component.subcomponents) == len(subcomponents) for new_subcomponent, old_subcomponent in zip(subcomponents, component.subcomponents): if new_subcomponent is not old_subcomponent: return self.copy_component(component, attributes, subcomponents) return component def copy_component(self, component, attributes, subcomponents): """Create a copy of the component with attributes and subcomponents.""" component = component.copy() for key, value in attributes.items(): component[key] = value assert len(component.subcomponents) == 0 for subcomponent in subcomponents: component.add_component(subcomponent) return component def walk(self, calendar): """Walk along the calendar and return the changed or identical object.""" subcomponents = [] for subcomponent in calendar.subcomponents: if isinstance(subcomponent, icalendar.cal.Event): subcomponent = self.walk_event(subcomponent) subcomponents.append(subcomponent) return self.copy_if_changed(calendar, {}, subcomponents) def walk_event(self, event): """Walk along the event and return the changed or identical object.""" attributes = {} for name in self.VALUE_ATTRIBUTES: value = event.get(name) if value is not None: attributes[name] = self.walk_value(value) return self.copy_if_changed(event, attributes, event.subcomponents) def walk_value_default(self, value): """Default method for walking along a value type.""" return value def walk_value(self, value): """Walk along a value type.""" name = "walk_value_" + type(value).__name__ walk = getattr(self, name, self.walk_value_default) return walk(value) def walk_value_list(self, l): """Walk through a list of values.""" v = list(map(self.walk_value, l)) if list_is(v, l): return l return v def walk_value_vDDDLists(self, l): dts = [ddd.dt for ddd in l.dts] new_dts = [self.walk_value(dt) for dt in dts] if list_is(new_dts, dts): return l return vDDDLists(new_dts) def walk_value_vDDDTypes(self, value): """Walk along an icalendar value type""" dt = self.walk_value(value.dt) if dt is value.dt: return value return vDDDTypes(dt) def walk_value_datetime(self, dt): """Walk along a datetime.datetime object.""" return dt def is_UTC(self, dt): """Return whether the time zone is a UTC time zone.""" if dt.tzname() is None: return False return dt.tzname().upper() == "UTC" def is_Floating(self, dt): return dt.tzname() is None def is_pytz(tzinfo): """Whether the time zone requires localize() and normalize(). pytz requires these funtions to be used in order to correctly use the time zones after operations. """ return hasattr(tzinfo , "localize") class UTCChangingWalker(CalendarWalker): """Changes the UTC time zone into a new time zone.""" def __init__(self, timezone): """Initialize the walker with the new time zone.""" self.new_timezone = timezone def walk_value_datetime(self, dt): """Walk along a datetime.datetime object.""" if self.is_UTC(dt): return dt.astimezone(self.new_timezone) elif self.is_Floating(dt): if is_pytz(self.new_timezone): return self.new_timezone.localize(dt) return dt.replace(tzinfo=self.new_timezone) return dt def to_standard(calendar : icalendar.Calendar, timezone:Optional[datetime.tzinfo]=None) -> icalendar.Calendar: """Make a calendar that might use X-WR-TIMEZONE compatible with RFC 5545. Arguments: - calendar is an icalendar.Calendar object. It does not need to have the X-WR-TIMEZONE property but if it has, calendar will be converted to conform to RFC 5545. - timezone is an optional timezone argument if you want to override the existence of the actual X-WR-TIMEZONE property of the calendar. This can be a string like "Europe/Berlin" or "UTC" or a pytz.timezone or any other timezone accepted by the datetime module. """ if timezone is None: timezone = calendar.get(X_WR_TIMEZONE, None) if timezone is not None and not isinstance(timezone, datetime.tzinfo): timezone = zoneinfo.ZoneInfo(timezone) if timezone is not None: walker = UTCChangingWalker(timezone) return walker.walk(calendar) return calendar def main(): """x-wr-timezone converts ICSfiles with X-WR-TIMEZONE to use RFC 5545 instead. Convert input: cat in.ics | x-wr-timezone > out.ics wget -O- https://example.org/in.ics | x-wr-timezone > out.ics curl https://example.org/in.ics | x-wr-timezone > out.ics Convert files: x-wr-timezone in.ics out.ics Get help: x-wr-timezone --help For bug reports, code and questions, visit the projet page: https://github.com/niccokunzmann/x-wr-timezone License: LPGLv3+ """ if len(sys.argv) == 1: in_file = getattr(sys.stdin, "buffer", sys.stdin) out_file = getattr(sys.stdout, "buffer", sys.stdout) elif len(sys.argv) == 3: in_file = open(sys.argv[1], 'rb') out_file = open(sys.argv[2], 'wb') else: sys.stdout.write(main.__doc__) return 0 input = in_file.read() calendar = icalendar.Calendar.from_ical(input) output = to_standard(calendar).to_ical() out_file.write(output) return 0 __all__ = [ "main", "to_standard", "UTCChangingWalker", "list_is", "X_WR_TIMEZONE", "CalendarWalker" ]