pax_global_header 0000666 0000000 0000000 00000000064 14676414732 0014530 g ustar 00root root 0000000 0000000 52 comment=e4af34587478a74674cc5b2c0784a88fd35897aa
x-wr-timezone-1.0.1/ 0000775 0000000 0000000 00000000000 14676414732 0014254 5 ustar 00root root 0000000 0000000 x-wr-timezone-1.0.1/.github/ 0000775 0000000 0000000 00000000000 14676414732 0015614 5 ustar 00root root 0000000 0000000 x-wr-timezone-1.0.1/.github/FUNDING.yml 0000664 0000000 0000000 00000001510 14676414732 0017426 0 ustar 00root root 0000000 0000000 # 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.yml 0000664 0000000 0000000 00000000766 14676414732 0020455 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14676414732 0017651 5 ustar 00root root 0000000 0000000 x-wr-timezone-1.0.1/.github/workflows/tests.yml 0000664 0000000 0000000 00000007260 14676414732 0021543 0 ustar 00root root 0000000 0000000 name: 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/.gitignore 0000664 0000000 0000000 00000000134 14676414732 0016242 0 ustar 00root root 0000000 0000000 /ENV
__pycache__
*.pyc
*egg-info
/build
/dist
.pytest_cache/
.vscode/
ENV2
*.swp
.tox
.venv
x-wr-timezone-1.0.1/.gitlab-ci.yml 0000664 0000000 0000000 00000011237 14676414732 0016714 0 ustar 00root root 0000000 0000000 # 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/LICENSE 0000664 0000000 0000000 00000016744 14676414732 0015275 0 ustar 00root root 0000000 0000000 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.in 0000664 0000000 0000000 00000000052 14676414732 0016007 0 ustar 00root root 0000000 0000000 include *requirements.txt
include LICENSE
x-wr-timezone-1.0.1/README.rst 0000664 0000000 0000000 00000020321 14676414732 0015741 0 ustar 00root root 0000000 0000000 X-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.sh 0000775 0000000 0000000 00000000561 14676414732 0016250 0 ustar 00root root 0000000 0000000 #!/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.txt 0000664 0000000 0000000 00000000021 14676414732 0017531 0 ustar 00root root 0000000 0000000 icalendar
tzdata
x-wr-timezone-1.0.1/setup.py 0000664 0000000 0000000 00000012541 14676414732 0015771 0 ustar 00root root 0000000 0000000 #!/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.txt 0000664 0000000 0000000 00000000075 14676414732 0020517 0 ustar 00root root 0000000 0000000 pytest
restructuredtext-lint
pygments
icalendar>=5.0.11
pytz
x-wr-timezone-1.0.1/test/ 0000775 0000000 0000000 00000000000 14676414732 0015233 5 ustar 00root root 0000000 0000000 x-wr-timezone-1.0.1/test/calendars/ 0000775 0000000 0000000 00000000000 14676414732 0017167 5 ustar 00root root 0000000 0000000 x-wr-timezone-1.0.1/test/calendars/Germany-Holidays-date-as-value-type.in.ics 0000664 0000000 0000000 00000033372 14676414732 0027065 0 ustar 00root root 0000000 0000000 BEGIN: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:VCALENDAR x-wr-timezone-1.0.1/test/calendars/Germany-Holidays-date-as-value-type.out.ics 0000664 0000000 0000000 00000033372 14676414732 0027266 0 ustar 00root root 0000000 0000000 BEGIN: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:VCALENDAR x-wr-timezone-1.0.1/test/calendars/exdate-hackerpublicradio-modified.in.ics 0000664 0000000 0000000 00000001523 14676414732 0026776 0 ustar 00root root 0000000 0000000 BEGIN: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.ics 0000664 0000000 0000000 00000002120 14676414732 0027171 0 ustar 00root root 0000000 0000000 BEGIN: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.ics 0000664 0000000 0000000 00000003163 14676414732 0024455 0 ustar 00root root 0000000 0000000 BEGIN: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.ics 0000664 0000000 0000000 00000003251 14676414732 0024654 0 ustar 00root root 0000000 0000000 BEGIN: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.ics 0000664 0000000 0000000 00000001507 14676414732 0025047 0 ustar 00root root 0000000 0000000 BEGIN: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.ics 0000664 0000000 0000000 00000002104 14676414732 0025242 0 ustar 00root root 0000000 0000000 BEGIN: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.ics 0000664 0000000 0000000 00000000712 14676414732 0023741 0 ustar 00root root 0000000 0000000 BEGIN: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.ics 0000664 0000000 0000000 00000000764 14676414732 0024151 0 ustar 00root root 0000000 0000000 BEGIN: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.ics 0000664 0000000 0000000 00000001165 14676414732 0027156 0 ustar 00root root 0000000 0000000 BEGIN: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.ics 0000664 0000000 0000000 00000001166 14676414732 0027360 0 ustar 00root root 0000000 0000000 BEGIN: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.ics 0000664 0000000 0000000 00000001631 14676414732 0025037 0 ustar 00root root 0000000 0000000 BEGIN: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.ics 0000664 0000000 0000000 00000001760 14676414732 0025243 0 ustar 00root root 0000000 0000000 BEGIN: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.ics 0000664 0000000 0000000 00000001367 14676414732 0025304 0 ustar 00root root 0000000 0000000 BEGIN: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.ics 0000664 0000000 0000000 00000001367 14676414732 0025505 0 ustar 00root root 0000000 0000000 BEGIN: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.py 0000664 0000000 0000000 00000013335 14676414732 0017437 0 ustar 00root root 0000000 0000000 """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.sh 0000775 0000000 0000000 00000000540 14676414732 0021312 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000000365 14676414732 0021275 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000013302 14676414732 0022221 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000004664 14676414732 0017630 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000000650 14676414732 0020463 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000001237 14676414732 0020104 0 ustar 00root root 0000000 0000000 '''
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_message x-wr-timezone-1.0.1/tox.ini 0000664 0000000 0000000 00000000763 14676414732 0015575 0 ustar 00root root 0000000 0000000 # 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.py 0000664 0000000 0000000 00000016614 14676414732 0017527 0 ustar 00root root 0000000 0000000 # 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"
]