summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/lint.yml12
-rw-r--r--.github/workflows/test.yml59
-rw-r--r--.gitignore1
-rw-r--r--.pre-commit-config.yaml14
-rw-r--r--.travis.yml26
-rw-r--r--CHANGES.txt30
-rw-r--r--LICENSE26
-rw-r--r--README.rst35
-rw-r--r--bootstrap.py266
-rw-r--r--buildout.cfg32
-rw-r--r--setup.cfg2
-rw-r--r--setup.py116
-rw-r--r--src/isodate/__init__.py97
-rw-r--r--src/isodate/duration.py295
-rw-r--r--src/isodate/isodates.py166
-rw-r--r--src/isodate/isodatetime.py51
-rw-r--r--src/isodate/isoduration.py117
-rw-r--r--src/isodate/isoerror.py32
-rw-r--r--src/isodate/isostrf.py221
-rw-r--r--src/isodate/isotime.py127
-rw-r--r--src/isodate/isotzinfo.py88
-rw-r--r--src/isodate/tests/__init__.py70
-rw-r--r--src/isodate/tests/test_date.py133
-rw-r--r--src/isodate/tests/test_datetime.py215
-rw-r--r--src/isodate/tests/test_duration.py634
-rw-r--r--src/isodate/tests/test_pickle.py42
-rw-r--r--src/isodate/tests/test_strf.py122
-rw-r--r--src/isodate/tests/test_time.py174
-rw-r--r--src/isodate/tzinfo.py91
-rw-r--r--tox.ini23
30 files changed, 1543 insertions, 1774 deletions
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..6f8c677
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,12 @@
+name: Lint
+
+on: [push, pull_request]
+
+jobs:
+ lint:
+ runs-on: ubuntu-20.04
+
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2
+ - uses: pre-commit/action@v2.0.2
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..098d327
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,59 @@
+name: Test
+
+on: [push, pull_request]
+
+env:
+ FORCE_COLOR: 1
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: ["3.7", "3.8", "3.9", "3.10", "pypy3"]
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ include:
+ # Include new variables for Codecov
+ - { codecov-flag: GHA_Ubuntu, os: ubuntu-latest }
+ - { codecov-flag: GHA_macOS, os: macos-latest }
+ - { codecov-flag: GHA_Windows, os: windows-latest }
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Get pip cache dir
+ id: pip-cache
+ run: |
+ echo "::set-output name=dir::$(pip cache dir)"
+
+ - name: Cache
+ uses: actions/cache@v2
+ with:
+ path: ${{ steps.pip-cache.outputs.dir }}
+ key:
+ ${{ matrix.os }}-${{ matrix.python-version }}-v1-${{
+ hashFiles('**/setup.py') }}
+ restore-keys: |
+ ${{ matrix.os }}-${{ matrix.python-version }}-v1-
+
+ - name: Install dependencies
+ run: |
+ python -m pip install -U pip
+ python -m pip install -U wheel
+ python -m pip install -U tox
+
+ - name: Tox tests
+ run: |
+ tox -e py
+
+ - name: Upload coverage
+ uses: codecov/codecov-action@v2
+ with:
+ flags: ${{ matrix.codecov-flag }}
+ name: ${{ matrix.os }} Python ${{ matrix.python-version }}
diff --git a/.gitignore b/.gitignore
index 8b04075..fdacade 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,4 +10,5 @@ develop-eggs
dist
parts
.coverage
+coverage.xml
htmlcov
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..c00dfd9
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,14 @@
+repos:
+
+ - repo: https://github.com/pycqa/flake8
+ rev: 3.9.1
+ hooks:
+ - id: flake8
+ args:
+ - "--max-line-length=88"
+
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v3.4.0
+ hooks:
+ - id: check-merge-conflict
+ - id: check-yaml
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index db01f82..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-language: python
-
-python:
- - 3.3
-
-env:
- - TOX_ENV=py26
- - TOX_ENV=py27
- - TOX_ENV=py32
- - TOX_ENV=py33
- - TOX_ENV=pypy
- - TOX_ENV=flake
- - TOX_ENV=cover
-
-install:
- - pip install tox
-
-script:
- - tox -e $TOX_ENV
-
-after_script:
- - if [ $TOX_ENV == "cover" ]; then
- pip install --quiet coveralls;
- coveralls;
- fi
diff --git a/CHANGES.txt b/CHANGES.txt
index 40ae79c..1bac7d5 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -2,6 +2,36 @@
CHANGES
=======
+0.7.0 (unreleased)
+------------------
+
+- drop end of life python versions
+- Don't match garbage characters at the end of parsed strings #16 (Gabriel de Perthuis)
+
+
+0.6.1 (2021-12-13)
+------------------
+
+- support python 3.10 (Hugo van Kemenade)
+- last version to support py 2.7
+
+
+0.6.0 (2017-10-13)
+------------------
+
+- support incomplete month date (Fabien Loffredo)
+- rely on duck typing when doing duration maths
+- support ':' as separator in fractional time zones (usrenmae)
+
+
+0.5.4 (2015-08-06)
+------------------
+
+- Fix parsing of Periods (Fabien Bochu)
+- Make Duration objects hashable (Geoffrey Fairchild)
+- Add multiplication to duration (Reinoud Elhorst)
+
+
0.5.1 (2014-11-07)
------------------
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ccd1d66
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2021, Hugo van Kemenade and contributors
+Copyright (c) 2009-2018, Gerhard Weis and contributors
+Copyright (c) 2009, Gerhard Weis
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.rst b/README.rst
index 67a0e51..63a56d5 100644
--- a/README.rst
+++ b/README.rst
@@ -2,20 +2,17 @@
ISO 8601 date/time parser
=========================
-.. image:: https://travis-ci.org/gweis/isodate.png?branch=master
+.. image:: https://travis-ci.org/gweis/isodate.svg?branch=master
:target: https://travis-ci.org/gweis/isodate
:alt: Travis-CI
-.. image:: https://coveralls.io/repos/gweis/isodate/badge.png?branch=master
+.. image:: https://coveralls.io/repos/gweis/isodate/badge.svg?branch=master
:target: https://coveralls.io/r/gweis/isodate?branch=master
:alt: Coveralls
-.. image:: https://pypip.in/v/isodate/badge.png
- :target: https://pypi.python.org/pypi/isodate/
+.. image:: https://img.shields.io/pypi/v/isodate.svg
+ :target: https://pypi.python.org/pypi/isodate/
:alt: Latest Version
-.. image:: https://pypip.in/d/isodate/badge.png
- :target: https://pypi.python.org/pypi//isodate/
- :alt: Downloads
-.. image:: https://pypip.in/license/isodate/badge.png
- :target: https://pypi.python.org/pypi/isodate/
+.. image:: https://img.shields.io/pypi/l/isodate.svg
+ :target: https://pypi.python.org/pypi/isodate/
:alt: License
@@ -41,7 +38,7 @@ for instance nanoseconds it will round it to microseconds.
Documentation
-------------
-Currently there are four parsing methods available.
+The following parsing methods are available.
* parse_time:
parses an ISO 8601 time string into a *time* object
* parse_date:
@@ -60,7 +57,7 @@ does not handle years and months, this module provides a *Duration* class,
which can be used almost like a *timedelta* object (with some limitations).
However, a *Duration* object can be converted into a *timedelta* object.
-There are also ISO formating methods for all supported data types. Each
+There are also ISO formatting methods for all supported data types. Each
*xxx_isoformat* method accepts a format parameter. The default format is
always the ISO 8601 expanded format. This is the same format used by
*datetime.isoformat*:
@@ -86,16 +83,16 @@ always the ISO 8601 expanded format. This is the same format used by
prior 1900. This method also understands how to format *datetime* and
*Duration* instances.
-Installation:
--------------
+Installation
+------------
This module can easily be installed with Python standard installation methods.
Either use *python setup.py install* or in case you have *setuptools* or
*distribute* available, you can also use *easy_install*.
-Limitations:
-------------
+Limitations
+-----------
* The parser accepts several date/time representation which should be invalid
according to ISO 8601 standard.
@@ -107,13 +104,13 @@ Limitations:
1901-01-01.
3. negative *Duration* and *timedelta* value are not fully supported yet.
-Further information:
---------------------
+Further information
+-------------------
The doc strings and unit tests should provide rather detailed information about
the methods and their limitations.
-The source release provides a *setup.py* script and a *buildout.cfg*. Both can
-be used to run the unit tests included.
+The source release provides a *setup.py* script,
+which can be used to run the unit tests included.
Source code is available at `<http://github.com/gweis/isodate>`_.
diff --git a/bootstrap.py b/bootstrap.py
deleted file mode 100644
index 716795f..0000000
--- a/bootstrap.py
+++ /dev/null
@@ -1,266 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2006 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Bootstrap a buildout-based project
-
-Simply run this script in a directory containing a buildout.cfg.
-The script accepts buildout command-line options, so you can
-use the -c option to specify an alternate configuration file.
-"""
-
-import os, shutil, sys, tempfile, urllib, urllib2, subprocess
-from optparse import OptionParser
-
-if sys.platform == 'win32':
- def quote(c):
- if ' ' in c:
- return '"%s"' % c # work around spawn lamosity on windows
- else:
- return c
-else:
- quote = str
-
-# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments.
-stdout, stderr = subprocess.Popen(
- [sys.executable, '-Sc',
- 'try:\n'
- ' import ConfigParser\n'
- 'except ImportError:\n'
- ' print 1\n'
- 'else:\n'
- ' print 0\n'],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
-has_broken_dash_S = bool(int(stdout.strip()))
-
-# In order to be more robust in the face of system Pythons, we want to
-# run without site-packages loaded. This is somewhat tricky, in
-# particular because Python 2.6's distutils imports site, so starting
-# with the -S flag is not sufficient. However, we'll start with that:
-if not has_broken_dash_S and 'site' in sys.modules:
- # We will restart with python -S.
- args = sys.argv[:]
- args[0:0] = [sys.executable, '-S']
- args = map(quote, args)
- os.execv(sys.executable, args)
-# Now we are running with -S. We'll get the clean sys.path, import site
-# because distutils will do it later, and then reset the path and clean
-# out any namespace packages from site-packages that might have been
-# loaded by .pth files.
-clean_path = sys.path[:]
-import site # imported because of its side effects
-sys.path[:] = clean_path
-for k, v in sys.modules.items():
- if k in ('setuptools', 'pkg_resources') or (
- hasattr(v, '__path__') and
- len(v.__path__) == 1 and
- not os.path.exists(os.path.join(v.__path__[0], '__init__.py'))):
- # This is a namespace package. Remove it.
- sys.modules.pop(k)
-
-is_jython = sys.platform.startswith('java')
-
-setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
-distribute_source = 'http://python-distribute.org/distribute_setup.py'
-
-
-# parsing arguments
-def normalize_to_url(option, opt_str, value, parser):
- if value:
- if '://' not in value: # It doesn't smell like a URL.
- value = 'file://%s' % (
- urllib.pathname2url(
- os.path.abspath(os.path.expanduser(value))),)
- if opt_str == '--download-base' and not value.endswith('/'):
- # Download base needs a trailing slash to make the world happy.
- value += '/'
- else:
- value = None
- name = opt_str[2:].replace('-', '_')
- setattr(parser.values, name, value)
-
-usage = '''\
-[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
-
-Bootstraps a buildout-based project.
-
-Simply run this script in a directory containing a buildout.cfg, using the
-Python that you want bin/buildout to use.
-
-Note that by using --setup-source and --download-base to point to
-local resources, you can keep this script from going over the network.
-'''
-
-parser = OptionParser(usage=usage)
-parser.add_option("-v", "--version", dest="version",
- help="use a specific zc.buildout version")
-parser.add_option("-d", "--distribute",
- action="store_true", dest="use_distribute", default=False,
- help="Use Distribute rather than Setuptools.")
-parser.add_option("--setup-source", action="callback", dest="setup_source",
- callback=normalize_to_url, nargs=1, type="string",
- help=("Specify a URL or file location for the setup file. "
- "If you use Setuptools, this will default to " +
- setuptools_source + "; if you use Distribute, this "
- "will default to " + distribute_source + "."))
-parser.add_option("--download-base", action="callback", dest="download_base",
- callback=normalize_to_url, nargs=1, type="string",
- help=("Specify a URL or directory for downloading "
- "zc.buildout and either Setuptools or Distribute. "
- "Defaults to PyPI."))
-parser.add_option("--eggs",
- help=("Specify a directory for storing eggs. Defaults to "
- "a temporary directory that is deleted when the "
- "bootstrap script completes."))
-parser.add_option("-t", "--accept-buildout-test-releases",
- dest='accept_buildout_test_releases',
- action="store_true", default=False,
- help=("Normally, if you do not specify a --version, the "
- "bootstrap script and buildout gets the newest "
- "*final* versions of zc.buildout and its recipes and "
- "extensions for you. If you use this flag, "
- "bootstrap and buildout will get the newest releases "
- "even if they are alphas or betas."))
-parser.add_option("-c", None, action="store", dest="config_file",
- help=("Specify the path to the buildout configuration "
- "file to be used."))
-
-options, orig_args = parser.parse_args()
-
-args = []
-
-# if -c was provided, we push it back into args for buildout's main function
-if options.config_file is not None:
- args += ['-c', options.config_file]
-
-if options.eggs:
- eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
-else:
- eggs_dir = tempfile.mkdtemp()
-
-if options.setup_source is None:
- if options.use_distribute:
- options.setup_source = distribute_source
- else:
- options.setup_source = setuptools_source
-
-if options.accept_buildout_test_releases:
- args.append('buildout:accept-buildout-test-releases=true')
-
-try:
- import pkg_resources
- import setuptools # A flag. Sometimes pkg_resources is installed alone.
- if not hasattr(pkg_resources, '_distribute'):
- raise ImportError
-except ImportError:
- ez_code = urllib2.urlopen(
- options.setup_source).read().replace('\r\n', '\n')
- ez = {}
- exec ez_code in ez
- setup_args = dict(to_dir=eggs_dir, download_delay=0)
- if options.download_base:
- setup_args['download_base'] = options.download_base
- if options.use_distribute:
- setup_args['no_fake'] = True
- ez['use_setuptools'](**setup_args)
- if 'pkg_resources' in sys.modules:
- reload(sys.modules['pkg_resources'])
- import pkg_resources
- # This does not (always?) update the default working set. We will
- # do it.
- for path in sys.path:
- if path not in pkg_resources.working_set.entries:
- pkg_resources.working_set.add_entry(path)
-
-cmd = [quote(sys.executable),
- '-c',
- quote('from setuptools.command.easy_install import main; main()'),
- '-mqNxd',
- quote(eggs_dir)]
-
-if not has_broken_dash_S:
- cmd.insert(1, '-S')
-
-find_links = options.download_base
-if not find_links:
- find_links = os.environ.get('bootstrap-testing-find-links')
-if find_links:
- cmd.extend(['-f', quote(find_links)])
-
-if options.use_distribute:
- setup_requirement = 'distribute'
-else:
- setup_requirement = 'setuptools'
-ws = pkg_resources.working_set
-setup_requirement_path = ws.find(
- pkg_resources.Requirement.parse(setup_requirement)).location
-env = dict(
- os.environ,
- PYTHONPATH=setup_requirement_path)
-
-requirement = 'zc.buildout'
-version = options.version
-if version is None and not options.accept_buildout_test_releases:
- # Figure out the most recent final version of zc.buildout.
- import setuptools.package_index
- _final_parts = '*final-', '*final'
-
- def _final_version(parsed_version):
- for part in parsed_version:
- if (part[:1] == '*') and (part not in _final_parts):
- return False
- return True
- index = setuptools.package_index.PackageIndex(
- search_path=[setup_requirement_path])
- if find_links:
- index.add_find_links((find_links,))
- req = pkg_resources.Requirement.parse(requirement)
- if index.obtain(req) is not None:
- best = []
- bestv = None
- for dist in index[req.project_name]:
- distv = dist.parsed_version
- if _final_version(distv):
- if bestv is None or distv > bestv:
- best = [dist]
- bestv = distv
- elif distv == bestv:
- best.append(dist)
- if best:
- best.sort()
- version = best[-1].version
-if version:
- requirement = '=='.join((requirement, version))
-cmd.append(requirement)
-
-if is_jython:
- import subprocess
- exitcode = subprocess.Popen(cmd, env=env).wait()
-else: # Windows prefers this, apparently; otherwise we would prefer subprocess
- exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
-if exitcode != 0:
- sys.stdout.flush()
- sys.stderr.flush()
- print ("An error occurred when trying to install zc.buildout. "
- "Look above this message for any errors that "
- "were output by easy_install.")
- sys.exit(exitcode)
-
-ws.add_entry(eggs_dir)
-ws.require(requirement)
-import zc.buildout.buildout
-if orig_args:
- # run buildout with commands passed to bootstrap.py, then actually bootstrap
- zc.buildout.buildout.main(args + orig_args)
-zc.buildout.buildout.main(args + ['bootstrap'])
-if not options.eggs: # clean up temporary egg directory
- shutil.rmtree(eggs_dir)
diff --git a/buildout.cfg b/buildout.cfg
deleted file mode 100644
index 5f8c446..0000000
--- a/buildout.cfg
+++ /dev/null
@@ -1,32 +0,0 @@
-[buildout]
-develop = .
-parts = isodate importchecker test pydev coverage coverage-report
-
-[isodate]
-recipe = zc.recipe.egg
-eggs = isodate
-interpreter = python
-
-[pydev]
-recipe = pb.recipes.pydev
-eggs = ${isodate:eggs}
-
-[test]
-recipe = zc.recipe.testrunner
-eggs = isodate
-
-[coverage]
-recipe = zc.recipe.testrunner
-eggs = isodate
-defaults = ['--coverage', '.']
-
-[coverage-report]
-recipe = zc.recipe.egg
-eggs = z3c.coverage
-scripts = coverage=coverage-report
-arguments = ('parts/coverage', 'parts/coverage-report')
-
-[importchecker]
-recipe = zc.recipe.egg
-eggs = importchecker
-arguments = "${buildout:directory}/src"
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..2a9acf1
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[bdist_wheel]
+universal = 1
diff --git a/setup.py b/setup.py
index 2e1a519..d5a3427 100644
--- a/setup.py
+++ b/setup.py
@@ -1,84 +1,42 @@
#!/usr/bin/env python
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
import os
-import sys
-
-setupargs = {}
-
-try:
- from setuptools import setup
- setupargs['test_suite'] = 'isodate.tests.test_suite'
- if sys.version[0] == '3':
- setupargs['use_2to3'] = True
-except ImportError:
- from distutils.core import setup
- if sys.version[0] == '3':
- from distutils.command.build_py import build_py_2to3
- setupargs['cmdclass'] = {'build_py': build_py_2to3}
+from setuptools import setup
def read(*rnames):
- return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
-
-setup(name='isodate',
- version='0.5.2-dev',
- packages=['isodate', 'isodate.tests'],
- package_dir={'': 'src'},
-
- # dependencies:
- # install_requires = [],
-
- # PyPI metadata
- author='Gerhard Weis',
- author_email='gerhard.weis@proclos.com',
- description='An ISO 8601 date/time/duration parser and formater',
- license='BSD',
- # keywords = '',
- url='http://cheeseshop.python.org/pypi/isodate',
-
- long_description=(read('README.rst') +
- read('CHANGES.txt') +
- read('TODO.txt')),
-
- classifiers=['Development Status :: 4 - Beta',
- # 'Environment :: Web Environment',
- 'Intended Audience :: Developers',
- 'License :: OSI Approved :: BSD License',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 2.6',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3.2',
- 'Programming Language :: Python :: 3.3',
- 'Programming Language :: Python :: 3.4',
- 'Programming Language :: Python :: Implementation :: PyPy',
- 'Topic :: Internet',
- ('Topic :: Software Development :'
- ': Libraries :: Python Modules'),
- ],
- **setupargs)
+ with open(os.path.join(os.path.dirname(__file__), *rnames)) as read_file:
+ return read_file.read()
+
+
+setup(
+ name="isodate",
+ version="0.7.0.dev0",
+ packages=["isodate", "isodate.tests"],
+ package_dir={"": "src"},
+ # PyPI metadata
+ author="Gerhard Weis",
+ author_email="gerhard.weis@proclos.com",
+ description="An ISO 8601 date/time/duration parser and formatter",
+ license="BSD-3-Clause",
+ license_files=("LICENSE",),
+ # keywords = '',
+ url="https://github.com/gweis/isodate/",
+ long_description=(read("README.rst") + read("CHANGES.txt") + read("TODO.txt")),
+ classifiers=[
+ "Development Status :: 4 - Beta",
+ # 'Environment :: Web Environment',
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: BSD License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: Implementation :: PyPy",
+ "Topic :: Internet",
+ ("Topic :: Software Development :" ": Libraries :: Python Modules"),
+ ],
+ test_suite="isodate.tests.test_suite",
+)
diff --git a/src/isodate/__init__.py b/src/isodate/__init__.py
index 26604c2..3b1e147 100644
--- a/src/isodate/__init__.py
+++ b/src/isodate/__init__.py
@@ -1,35 +1,9 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
Import all essential functions and constants to re-export them here for easy
access.
This module contains also various pre-defined ISO 8601 format strings.
-'''
+"""
from isodate.isodates import parse_date, date_isoformat
from isodate.isotime import parse_time, time_isoformat
from isodate.isodatetime import parse_datetime, datetime_isoformat
@@ -43,7 +17,8 @@ from isodate.isostrf import DATE_BAS_COMPLETE, DATE_BAS_ORD_COMPLETE
from isodate.isostrf import DATE_BAS_WEEK, DATE_BAS_WEEK_COMPLETE
from isodate.isostrf import DATE_CENTURY, DATE_EXT_COMPLETE
from isodate.isostrf import DATE_EXT_ORD_COMPLETE, DATE_EXT_WEEK
-from isodate.isostrf import DATE_EXT_WEEK_COMPLETE, DATE_MONTH, DATE_YEAR
+from isodate.isostrf import DATE_EXT_WEEK_COMPLETE, DATE_YEAR
+from isodate.isostrf import DATE_BAS_MONTH, DATE_EXT_MONTH
from isodate.isostrf import TIME_BAS_COMPLETE, TIME_BAS_MINUTE
from isodate.isostrf import TIME_EXT_COMPLETE, TIME_EXT_MINUTE
from isodate.isostrf import TIME_HOUR
@@ -54,17 +29,53 @@ from isodate.isostrf import DT_BAS_WEEK_COMPLETE, DT_EXT_WEEK_COMPLETE
from isodate.isostrf import D_DEFAULT, D_WEEK, D_ALT_EXT, D_ALT_BAS
from isodate.isostrf import D_ALT_BAS_ORD, D_ALT_EXT_ORD
-__all__ = (parse_date, date_isoformat, parse_time, time_isoformat,
- parse_datetime, datetime_isoformat, parse_duration,
- duration_isoformat, ISO8601Error, parse_tzinfo,
- tz_isoformat, UTC, FixedOffset, LOCAL, Duration,
- strftime, DATE_BAS_COMPLETE, DATE_BAS_ORD_COMPLETE,
- DATE_BAS_WEEK, DATE_BAS_WEEK_COMPLETE, DATE_CENTURY,
- DATE_EXT_COMPLETE, DATE_EXT_ORD_COMPLETE, DATE_EXT_WEEK,
- DATE_EXT_WEEK_COMPLETE, DATE_MONTH, DATE_YEAR,
- TIME_BAS_COMPLETE, TIME_BAS_MINUTE, TIME_EXT_COMPLETE,
- TIME_EXT_MINUTE, TIME_HOUR, TZ_BAS, TZ_EXT, TZ_HOUR,
- DT_BAS_COMPLETE, DT_EXT_COMPLETE, DT_BAS_ORD_COMPLETE,
- DT_EXT_ORD_COMPLETE, DT_BAS_WEEK_COMPLETE,
- DT_EXT_WEEK_COMPLETE, D_DEFAULT, D_WEEK, D_ALT_EXT,
- D_ALT_BAS, D_ALT_BAS_ORD, D_ALT_EXT_ORD)
+__all__ = [
+ "parse_date",
+ "date_isoformat",
+ "parse_time",
+ "time_isoformat",
+ "parse_datetime",
+ "datetime_isoformat",
+ "parse_duration",
+ "duration_isoformat",
+ "ISO8601Error",
+ "parse_tzinfo",
+ "tz_isoformat",
+ "UTC",
+ "FixedOffset",
+ "LOCAL",
+ "Duration",
+ "strftime",
+ "DATE_BAS_COMPLETE",
+ "DATE_BAS_ORD_COMPLETE",
+ "DATE_BAS_WEEK",
+ "DATE_BAS_WEEK_COMPLETE",
+ "DATE_CENTURY",
+ "DATE_EXT_COMPLETE",
+ "DATE_EXT_ORD_COMPLETE",
+ "DATE_EXT_WEEK",
+ "DATE_EXT_WEEK_COMPLETE",
+ "DATE_YEAR",
+ "DATE_BAS_MONTH",
+ "DATE_EXT_MONTH",
+ "TIME_BAS_COMPLETE",
+ "TIME_BAS_MINUTE",
+ "TIME_EXT_COMPLETE",
+ "TIME_EXT_MINUTE",
+ "TIME_HOUR",
+ "TZ_BAS",
+ "TZ_EXT",
+ "TZ_HOUR",
+ "DT_BAS_COMPLETE",
+ "DT_EXT_COMPLETE",
+ "DT_BAS_ORD_COMPLETE",
+ "DT_EXT_ORD_COMPLETE",
+ "DT_BAS_WEEK_COMPLETE",
+ "DT_EXT_WEEK_COMPLETE",
+ "D_DEFAULT",
+ "D_WEEK",
+ "D_ALT_EXT",
+ "D_ALT_BAS",
+ "D_ALT_BAS_ORD",
+ "D_ALT_EXT_ORD",
+]
diff --git a/src/isodate/duration.py b/src/isodate/duration.py
index b44faa8..3803d1c 100644
--- a/src/isodate/duration.py
+++ b/src/isodate/duration.py
@@ -1,44 +1,18 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
This module defines a Duration class.
The class Duration allows to define durations in years and months and can be
used as limited replacement for timedelta objects.
-'''
-from datetime import date, datetime, timedelta
+"""
+from datetime import timedelta
from decimal import Decimal, ROUND_FLOOR
def fquotmod(val, low, high):
- '''
+ """
A divmod function with boundaries.
- '''
+ """
# assumes that all the maths is done with Decimals.
# divmod for Decimal uses truncate instead of floor as builtin
# divmod, so we have to do it manually here.
@@ -52,9 +26,9 @@ def fquotmod(val, low, high):
def max_days_in_month(year, month):
- '''
+ """
Determines the number of days of a specific month in a specific year.
- '''
+ """
if month in (1, 3, 5, 7, 8, 10, 12):
return 31
if month in (4, 6, 9, 11):
@@ -65,7 +39,7 @@ def max_days_in_month(year, month):
class Duration(object):
- '''
+ """
A class which represents a duration.
The difference to datetime.timedelta is, that this class handles also
@@ -85,21 +59,32 @@ class Duration(object):
The algorithm to add a duration to a date is defined at
http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes
- '''
+ """
- def __init__(self, days=0, seconds=0, microseconds=0, milliseconds=0,
- minutes=0, hours=0, weeks=0, months=0, years=0):
- '''
+ def __init__(
+ self,
+ days=0,
+ seconds=0,
+ microseconds=0,
+ milliseconds=0,
+ minutes=0,
+ hours=0,
+ weeks=0,
+ months=0,
+ years=0,
+ ):
+ """
Initialise this Duration instance with the given parameters.
- '''
+ """
if not isinstance(months, Decimal):
months = Decimal(str(months))
if not isinstance(years, Decimal):
years = Decimal(str(years))
self.months = months
self.years = years
- self.tdelta = timedelta(days, seconds, microseconds, milliseconds,
- minutes, hours, weeks)
+ self.tdelta = timedelta(
+ days, seconds, microseconds, milliseconds, minutes, hours, weeks
+ )
def __getstate__(self):
return self.__dict__
@@ -108,31 +93,46 @@ class Duration(object):
self.__dict__.update(state)
def __getattr__(self, name):
- '''
+ """
Provide direct access to attributes of included timedelta instance.
- '''
+ """
return getattr(self.tdelta, name)
def __str__(self):
- '''
+ """
Return a string representation of this duration similar to timedelta.
- '''
+ """
params = []
if self.years:
- params.append('%d years' % self.years)
+ params.append("%d years" % self.years)
if self.months:
- params.append('%d months' % self.months)
+ fmt = "%d months"
+ if self.months <= 1:
+ fmt = "%d month"
+ params.append(fmt % self.months)
params.append(str(self.tdelta))
- return ', '.join(params)
+ return ", ".join(params)
def __repr__(self):
- '''
+ """
Return a string suitable for repr(x) calls.
- '''
+ """
return "%s.%s(%d, %d, %d, years=%d, months=%d)" % (
- self.__class__.__module__, self.__class__.__name__,
- self.tdelta.days, self.tdelta.seconds,
- self.tdelta.microseconds, self.years, self.months)
+ self.__class__.__module__,
+ self.__class__.__name__,
+ self.tdelta.days,
+ self.tdelta.seconds,
+ self.tdelta.microseconds,
+ self.years,
+ self.months,
+ )
+
+ def __hash__(self):
+ """
+ Return a hash of this instance so that it can be used in, for
+ example, dicts and sets.
+ """
+ return hash((self.tdelta, self.months, self.years))
def __neg__(self):
"""
@@ -145,24 +145,24 @@ class Duration(object):
return negduration
def __add__(self, other):
- '''
+ """
Durations can be added with Duration, timedelta, date and datetime
objects.
- '''
- if isinstance(other, timedelta):
- newduration = Duration(years=self.years, months=self.months)
- newduration.tdelta = self.tdelta + other
- return newduration
+ """
if isinstance(other, Duration):
- newduration = Duration(years=self.years + other.years,
- months=self.months + other.months)
+ newduration = Duration(
+ years=self.years + other.years, months=self.months + other.months
+ )
newduration.tdelta = self.tdelta + other.tdelta
return newduration
- if isinstance(other, (date, datetime)):
- if (not(float(self.years).is_integer()
- and float(self.months).is_integer())):
- raise ValueError('fractional years or months not supported'
- ' for date calculations')
+ try:
+ # try anything that looks like a date or datetime
+ # 'other' has attributes year, month, day
+ # and relies on 'timedelta + other' being implemented
+ if not (float(self.years).is_integer() and float(self.months).is_integer()):
+ raise ValueError(
+ "fractional years or months not supported" " for date calculations"
+ )
newmonth = other.month + self.months
carry, newmonth = fquotmod(newmonth, 1, 13)
newyear = other.year + self.years + carry
@@ -171,65 +171,83 @@ class Duration(object):
newday = maxdays
else:
newday = other.day
- newdt = other.replace(year=newyear, month=newmonth, day=newday)
+ newdt = other.replace(
+ year=int(newyear), month=int(newmonth), day=int(newday)
+ )
+ # does a timedelta + date/datetime
return self.tdelta + newdt
- raise TypeError('unsupported operand type(s) for +: %s and %s' %
- (self.__class__, other.__class__))
-
- def __radd__(self, other):
- '''
- Add durations to timedelta, date and datetime objects.
- '''
- if isinstance(other, timedelta):
+ except AttributeError:
+ # other probably was not a date/datetime compatible object
+ pass
+ try:
+ # try if other is a timedelta
+ # relies on timedelta + timedelta supported
newduration = Duration(years=self.years, months=self.months)
newduration.tdelta = self.tdelta + other
return newduration
- if isinstance(other, (date, datetime)):
- if (not(float(self.years).is_integer()
- and float(self.months).is_integer())):
- raise ValueError('fractional years or months not supported'
- ' for date calculations')
- newmonth = other.month + self.months
- carry, newmonth = fquotmod(newmonth, 1, 13)
- newyear = other.year + self.years + carry
- maxdays = max_days_in_month(newyear, newmonth)
- if other.day > maxdays:
- newday = maxdays
- else:
- newday = other.day
- newdt = other.replace(year=newyear, month=newmonth, day=newday)
- return newdt + self.tdelta
- raise TypeError('unsupported operand type(s) for +: %s and %s' %
- (other.__class__, self.__class__))
+ except AttributeError:
+ # ignore ... other probably was not a timedelta compatible object
+ pass
+ # we have tried everything .... return a NotImplemented
+ return NotImplemented
+
+ __radd__ = __add__
+
+ def __mul__(self, other):
+ if isinstance(other, int):
+ newduration = Duration(years=self.years * other, months=self.months * other)
+ newduration.tdelta = self.tdelta * other
+ return newduration
+ return NotImplemented
+
+ __rmul__ = __mul__
def __sub__(self, other):
- '''
+ """
It is possible to subtract Duration and timedelta objects from Duration
objects.
- '''
+ """
if isinstance(other, Duration):
- newduration = Duration(years=self.years - other.years,
- months=self.months - other.months)
+ newduration = Duration(
+ years=self.years - other.years, months=self.months - other.months
+ )
newduration.tdelta = self.tdelta - other.tdelta
return newduration
- if isinstance(other, timedelta):
+ try:
+ # do maths with our timedelta object ....
newduration = Duration(years=self.years, months=self.months)
newduration.tdelta = self.tdelta - other
return newduration
- raise TypeError('unsupported operand type(s) for -: %s and %s' %
- (self.__class__, other.__class__))
+ except TypeError:
+ # looks like timedelta - other is not implemented
+ pass
+ return NotImplemented
def __rsub__(self, other):
- '''
+ """
It is possible to subtract Duration objecs from date, datetime and
timedelta objects.
- '''
- # print '__rsub__:', self, other
- if isinstance(other, (date, datetime)):
- if (not(float(self.years).is_integer()
- and float(self.months).is_integer())):
- raise ValueError('fractional years or months not supported'
- ' for date calculations')
+
+ TODO: there is some weird behaviour in date - timedelta ...
+ if timedelta has seconds or microseconds set, then
+ date - timedelta != date + (-timedelta)
+ for now we follow this behaviour to avoid surprises when mixing
+ timedeltas with Durations, but in case this ever changes in
+ the stdlib we can just do:
+ return -self + other
+ instead of all the current code
+ """
+ if isinstance(other, timedelta):
+ tmpdur = Duration()
+ tmpdur.tdelta = other
+ return tmpdur - self
+ try:
+ # check if other behaves like a date/datetime object
+ # does it have year, month, day and replace?
+ if not (float(self.years).is_integer() and float(self.months).is_integer()):
+ raise ValueError(
+ "fractional years or months not supported" " for date calculations"
+ )
newmonth = other.month - self.months
carry, newmonth = fquotmod(newmonth, 1, 13)
newyear = other.year - self.years + carry
@@ -238,55 +256,56 @@ class Duration(object):
newday = maxdays
else:
newday = other.day
- newdt = other.replace(year=newyear, month=newmonth, day=newday)
+ newdt = other.replace(
+ year=int(newyear), month=int(newmonth), day=int(newday)
+ )
return newdt - self.tdelta
- if isinstance(other, timedelta):
- tmpdur = Duration()
- tmpdur.tdelta = other
- return tmpdur - self
- raise TypeError('unsupported operand type(s) for -: %s and %s' %
- (other.__class__, self.__class__))
+ except AttributeError:
+ # other probably was not compatible with data/datetime
+ pass
+ return NotImplemented
def __eq__(self, other):
- '''
+ """
If the years, month part and the timedelta part are both equal, then
the two Durations are considered equal.
- '''
- if ((isinstance(other, timedelta) and
- self.years == 0 and self.months == 0)):
+ """
+ if isinstance(other, Duration):
+ if (self.years * 12 + self.months) == (
+ other.years * 12 + other.months
+ ) and self.tdelta == other.tdelta:
+ return True
+ return False
+ # check if other con be compared against timedelta object
+ # will raise an AssertionError when optimisation is off
+ if self.years == 0 and self.months == 0:
return self.tdelta == other
- if not isinstance(other, Duration):
- return NotImplemented
- if (((self.years * 12 + self.months) ==
- (other.years * 12 + other.months)
- and self.tdelta == other.tdelta)):
- return True
return False
def __ne__(self, other):
- '''
+ """
If the years, month part or the timedelta part is not equal, then
the two Durations are considered not equal.
- '''
- if ((isinstance(other, timedelta)
- and self.years == 0
- and self.months == 0)):
+ """
+ if isinstance(other, Duration):
+ if (self.years * 12 + self.months) != (
+ other.years * 12 + other.months
+ ) or self.tdelta != other.tdelta:
+ return True
+ return False
+ # check if other can be compared against timedelta object
+ # will raise an AssertionError when optimisation is off
+ if self.years == 0 and self.months == 0:
return self.tdelta != other
- if not isinstance(other, Duration):
- return NotImplemented
- if (((self.years * 12 + self.months) !=
- (other.years * 12 + other.months)
- or self.tdelta != other.tdelta)):
- return True
- return False
+ return True
def totimedelta(self, start=None, end=None):
- '''
+ """
Convert this duration into a timedelta object.
This method requires a start datetime or end datetimem, but raises
an exception if both are given.
- '''
+ """
if start is None and end is None:
raise ValueError("start or end required")
if start is not None and end is not None:
diff --git a/src/isodate/isodates.py b/src/isodate/isodates.py
index a58a2c0..3c5c095 100644
--- a/src/isodate/isodates.py
+++ b/src/isodate/isodates.py
@@ -1,37 +1,11 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
This modules provides a method to parse an ISO 8601:2004 date string to a
python datetime.date instance.
It supports all basic, extended and expanded formats as described in the ISO
standard. The only limitations it has, are given by the Python datetime.date
implementation, which does not support dates before 0001-01-01.
-'''
+"""
import re
from datetime import date, timedelta
@@ -46,7 +20,7 @@ DATE_REGEX_CACHE = {}
def build_date_regexps(yeardigits=4, expanded=False):
- '''
+ """
Compile set of regular expressions to parse ISO dates. The expressions will
be created only if they are not already in REGEX_CACHE.
@@ -56,7 +30,7 @@ def build_date_regexps(yeardigits=4, expanded=False):
ISO 8601 allows more than 4 digit years, on prior agreement, but then a +/-
sign is required (expanded format). To support +/- sign for 4 digit years,
the expanded parameter needs to be set to True.
- '''
+ """
if yeardigits != 4:
expanded = True
if (yeardigits, expanded) not in DATE_REGEX_CACHE:
@@ -67,59 +41,82 @@ def build_date_regexps(yeardigits=4, expanded=False):
sign = 1
else:
sign = 0
+
def add_re(regex_text):
- cache_entry.append(re.compile('\A' + regex_text + '\Z'))
+ cache_entry.append(re.compile(r"\A" + regex_text + r"\Z"))
+
# 1. complete dates:
# YYYY-MM-DD or +- YYYYYY-MM-DD... extended date format
- add_re(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
- r"-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})"
- % (sign, yeardigits))
+ add_re(
+ r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
+ r"-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})" % (sign, yeardigits)
+ )
# YYYYMMDD or +- YYYYYYMMDD... basic date format
- add_re(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
- r"(?P<month>[0-9]{2})(?P<day>[0-9]{2})"
- % (sign, yeardigits))
+ add_re(
+ r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
+ r"(?P<month>[0-9]{2})(?P<day>[0-9]{2})" % (sign, yeardigits)
+ )
# 2. complete week dates:
# YYYY-Www-D or +-YYYYYY-Www-D ... extended week date
- add_re(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
- r"-W(?P<week>[0-9]{2})-(?P<day>[0-9]{1})"
- % (sign, yeardigits))
+ add_re(
+ r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
+ r"-W(?P<week>[0-9]{2})-(?P<day>[0-9]{1})" % (sign, yeardigits)
+ )
# YYYYWwwD or +-YYYYYYWwwD ... basic week date
- add_re(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W"
- r"(?P<week>[0-9]{2})(?P<day>[0-9]{1})"
- % (sign, yeardigits))
+ add_re(
+ r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W"
+ r"(?P<week>[0-9]{2})(?P<day>[0-9]{1})" % (sign, yeardigits)
+ )
# 3. ordinal dates:
# YYYY-DDD or +-YYYYYY-DDD ... extended format
+ add_re(
+ r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
+ r"-(?P<day>[0-9]{3})" % (sign, yeardigits)
+ )
# YYYYDDD or +-YYYYYYDDD ... basic format
- add_re(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
- r"-?(?P<day>[0-9]{3})"
- % (sign, yeardigits))
+ add_re(
+ r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
+ r"(?P<day>[0-9]{3})" % (sign, yeardigits)
+ )
+ # 4. week dates:
+ # YYYY-Www or +-YYYYYY-Www ... extended reduced accuracy week date
# 4. week dates:
# YYYY-Www or +-YYYYYY-Www ... extended reduced accuracy week date
+ add_re(
+ r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
+ r"-W(?P<week>[0-9]{2})" % (sign, yeardigits)
+ )
# YYYYWww or +-YYYYYYWww ... basic reduced accuracy week date
- add_re(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
- r"-?W(?P<week>[0-9]{2})"
- % (sign, yeardigits))
+ add_re(
+ r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W"
+ r"(?P<week>[0-9]{2})" % (sign, yeardigits)
+ )
+ # 5. month dates:
+ # YYY-MM or +-YYYYYY-MM ... reduced accuracy specific month
# 5. month dates:
# YYY-MM or +-YYYYYY-MM ... reduced accuracy specific month
- add_re(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
- r"-(?P<month>[0-9]{2})"
- % (sign, yeardigits))
+ add_re(
+ r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
+ r"-(?P<month>[0-9]{2})" % (sign, yeardigits)
+ )
+ # YYYMM or +-YYYYYYMM ... basic incomplete month date format
+ add_re(
+ r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
+ r"(?P<month>[0-9]{2})" % (sign, yeardigits)
+ )
# 6. year dates:
# YYYY or +-YYYYYY ... reduced accuracy specific year
- add_re(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
- % (sign, yeardigits))
+ add_re(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" % (sign, yeardigits))
# 7. century dates:
# YY or +-YYYY ... reduced accuracy specific century
- add_re(r"(?P<sign>[+-]){%d}"
- r"(?P<century>[0-9]{%d})"
- % (sign, yeardigits - 2))
+ add_re(r"(?P<sign>[+-]){%d}" r"(?P<century>[0-9]{%d})" % (sign, yeardigits - 2))
DATE_REGEX_CACHE[(yeardigits, expanded)] = cache_entry
return DATE_REGEX_CACHE[(yeardigits, expanded)]
-def parse_date(datestring, yeardigits=4, expanded=False):
- '''
+def parse_date(datestring, yeardigits=4, expanded=False, defaultmonth=1, defaultday=1):
+ """
Parse an ISO 8601 date string into a datetime.date object.
As the datetime.date implementation is limited to dates starting from
@@ -139,6 +136,7 @@ def parse_date(datestring, yeardigits=4, expanded=False):
YYYY-DDD +-YYYYYY-DDD extended ordinal date
YYYYWww +-YYYYYYWww basic incomplete week date
YYYY-Www +-YYYYYY-Www extended incomplete week date
+ YYYMM +-YYYYYYMM basic incomplete month date
YYY-MM +-YYYYYY-MM incomplete month date
YYYY +-YYYYYY incomplete year date
YY +-YYYY incomplete century date
@@ -151,7 +149,7 @@ def parse_date(datestring, yeardigits=4, expanded=False):
@return: a datetime.date instance represented by datestring
@raise ISO8601Error: if this function can not parse the datestring
@raise ValueError: if datestring can not be represented by datetime.date
- '''
+ """
if yeardigits != 4:
expanded = True
isodates = build_date_regexps(yeardigits, expanded)
@@ -161,40 +159,44 @@ def parse_date(datestring, yeardigits=4, expanded=False):
groups = match.groupdict()
# sign, century, year, month, week, day,
# FIXME: negative dates not possible with python standard types
- sign = (groups['sign'] == '-' and -1) or 1
- if 'century' in groups:
- return date(sign * (int(groups['century']) * 100 + 1), 1, 1)
- if 'month' not in groups: # weekdate or ordinal date
- ret = date(sign * int(groups['year']), 1, 1)
- if 'week' in groups:
+ sign = (groups["sign"] == "-" and -1) or 1
+ if "century" in groups:
+ return date(
+ sign * (int(groups["century"]) * 100 + 1), defaultmonth, defaultday
+ )
+ if "month" not in groups: # weekdate or ordinal date
+ ret = date(sign * int(groups["year"]), 1, 1)
+ if "week" in groups:
isotuple = ret.isocalendar()
- if 'day' in groups:
- days = int(groups['day'] or 1)
+ if "day" in groups:
+ days = int(groups["day"] or 1)
else:
days = 1
# if first week in year, do weeks-1
- return ret + timedelta(weeks=int(groups['week']) -
- (((isotuple[1] == 1) and 1) or 0),
- days=-isotuple[2] + days)
- elif 'day' in groups: # ordinal date
- return ret + timedelta(days=int(groups['day'])-1)
+ return ret + timedelta(
+ weeks=int(groups["week"]) - (((isotuple[1] == 1) and 1) or 0),
+ days=-isotuple[2] + days,
+ )
+ elif "day" in groups: # ordinal date
+ return ret + timedelta(days=int(groups["day"]) - 1)
else: # year date
- return ret
+ return ret.replace(month=defaultmonth, day=defaultday)
# year-, month-, or complete date
- if 'day' not in groups or groups['day'] is None:
- day = 1
+ if "day" not in groups or groups["day"] is None:
+ day = defaultday
else:
- day = int(groups['day'])
- return date(sign * int(groups['year']),
- int(groups['month']) or 1, day)
- raise ISO8601Error('Unrecognised ISO 8601 date format: %r' % datestring)
+ day = int(groups["day"])
+ return date(
+ sign * int(groups["year"]), int(groups["month"]) or defaultmonth, day
+ )
+ raise ISO8601Error("Unrecognised ISO 8601 date format: %r" % datestring)
def date_isoformat(tdate, format=DATE_EXT_COMPLETE, yeardigits=4):
- '''
+ """
Format date strings.
This method is just a wrapper around isodate.isostrf.strftime and uses
Date-Extended-Complete as default format.
- '''
+ """
return strftime(tdate, format, yeardigits)
diff --git a/src/isodate/isodatetime.py b/src/isodate/isodatetime.py
index 9a5fce0..68adc7e 100644
--- a/src/isodate/isodatetime.py
+++ b/src/isodate/isodatetime.py
@@ -1,35 +1,9 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
This module defines a method to parse an ISO 8601:2004 date time string.
For this job it uses the parse_date and parse_time methods defined in date
and time module.
-'''
+"""
from datetime import datetime
from isodate.isostrf import strftime
@@ -40,29 +14,32 @@ from isodate.isotime import parse_time
def parse_datetime(datetimestring):
- '''
+ """
Parses ISO 8601 date-times into datetime.datetime objects.
This function uses parse_date and parse_time to do the job, so it allows
more combinations of date and time representations, than the actual
ISO 8601:2004 standard allows.
- '''
+ """
try:
- datestring, timestring = datetimestring.split('T')
+ datestring, timestring = datetimestring.split("T")
except ValueError:
- raise ISO8601Error("ISO 8601 time designator 'T' missing. Unable to"
- " parse datetime string %r" % datetimestring)
+ raise ISO8601Error(
+ "ISO 8601 time designator 'T' missing. Unable to"
+ " parse datetime string %r" % datetimestring
+ )
tmpdate = parse_date(datestring)
tmptime = parse_time(timestring)
return datetime.combine(tmpdate, tmptime)
-def datetime_isoformat(tdt, format=DATE_EXT_COMPLETE + 'T' +
- TIME_EXT_COMPLETE + TZ_EXT):
- '''
+def datetime_isoformat(
+ tdt, format=DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT
+):
+ """
Format datetime strings.
This method is just a wrapper around isodate.isostrf.strftime and uses
Extended-Complete as default format.
- '''
+ """
return strftime(tdt, format)
diff --git a/src/isodate/isoduration.py b/src/isodate/isoduration.py
index af44661..2218e06 100644
--- a/src/isodate/isoduration.py
+++ b/src/isodate/isoduration.py
@@ -1,35 +1,9 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
This module provides an ISO 8601:2004 duration parser.
It also provides a wrapper to strftime. This wrapper makes it easier to
format timedelta or Duration instances as ISO conforming strings.
-'''
+"""
from datetime import timedelta
from decimal import Decimal
import re
@@ -41,13 +15,15 @@ from isodate.isostrf import strftime, D_DEFAULT
ISO8601_PERIOD_REGEX = re.compile(
r"^(?P<sign>[+-])?"
- r"P(?P<years>[0-9]+([,.][0-9]+)?Y)?"
+ r"P(?!\b)"
+ r"(?P<years>[0-9]+([,.][0-9]+)?Y)?"
r"(?P<months>[0-9]+([,.][0-9]+)?M)?"
r"(?P<weeks>[0-9]+([,.][0-9]+)?W)?"
r"(?P<days>[0-9]+([,.][0-9]+)?D)?"
r"((?P<separator>T)(?P<hours>[0-9]+([,.][0-9]+)?H)?"
r"(?P<minutes>[0-9]+([,.][0-9]+)?M)?"
- r"(?P<seconds>[0-9]+([,.][0-9]+)?S)?)?$")
+ r"(?P<seconds>[0-9]+([,.][0-9]+)?S)?)?$"
+)
# regular expression to parse ISO duartion strings.
@@ -79,7 +55,7 @@ def parse_duration(datestring):
The alternative format does not support durations with years, months or
days set to 0.
"""
- if not isinstance(datestring, basestring):
+ if not isinstance(datestring, str):
raise TypeError("Expecting a string %r" % datestring)
match = ISO8601_PERIOD_REGEX.match(datestring)
if not match:
@@ -88,61 +64,82 @@ def parse_duration(datestring):
durdt = parse_datetime(datestring[1:])
if durdt.year != 0 or durdt.month != 0:
# create Duration
- ret = Duration(days=durdt.day, seconds=durdt.second,
- microseconds=durdt.microsecond,
- minutes=durdt.minute, hours=durdt.hour,
- months=durdt.month, years=durdt.year)
+ ret = Duration(
+ days=durdt.day,
+ seconds=durdt.second,
+ microseconds=durdt.microsecond,
+ minutes=durdt.minute,
+ hours=durdt.hour,
+ months=durdt.month,
+ years=durdt.year,
+ )
else: # FIXME: currently not possible in alternative format
# create timedelta
- ret = timedelta(days=durdt.day, seconds=durdt.second,
- microseconds=durdt.microsecond,
- minutes=durdt.minute, hours=durdt.hour)
+ ret = timedelta(
+ days=durdt.day,
+ seconds=durdt.second,
+ microseconds=durdt.microsecond,
+ minutes=durdt.minute,
+ hours=durdt.hour,
+ )
return ret
raise ISO8601Error("Unable to parse duration string %r" % datestring)
groups = match.groupdict()
for key, val in groups.items():
- if key not in ('separator', 'sign'):
+ if key not in ("separator", "sign"):
if val is None:
groups[key] = "0n"
# print groups[key]
- if key in ('years', 'months'):
- groups[key] = Decimal(groups[key][:-1].replace(',', '.'))
+ if key in ("years", "months"):
+ groups[key] = Decimal(groups[key][:-1].replace(",", "."))
else:
# these values are passed into a timedelta object,
# which works with floats.
- groups[key] = float(groups[key][:-1].replace(',', '.'))
+ groups[key] = float(groups[key][:-1].replace(",", "."))
if groups["years"] == 0 and groups["months"] == 0:
- ret = timedelta(days=groups["days"], hours=groups["hours"],
- minutes=groups["minutes"], seconds=groups["seconds"],
- weeks=groups["weeks"])
- if groups["sign"] == '-':
+ ret = timedelta(
+ days=groups["days"],
+ hours=groups["hours"],
+ minutes=groups["minutes"],
+ seconds=groups["seconds"],
+ weeks=groups["weeks"],
+ )
+ if groups["sign"] == "-":
ret = timedelta(0) - ret
else:
- ret = Duration(years=groups["years"], months=groups["months"],
- days=groups["days"], hours=groups["hours"],
- minutes=groups["minutes"], seconds=groups["seconds"],
- weeks=groups["weeks"])
- if groups["sign"] == '-':
+ ret = Duration(
+ years=groups["years"],
+ months=groups["months"],
+ days=groups["days"],
+ hours=groups["hours"],
+ minutes=groups["minutes"],
+ seconds=groups["seconds"],
+ weeks=groups["weeks"],
+ )
+ if groups["sign"] == "-":
ret = Duration(0) - ret
return ret
def duration_isoformat(tduration, format=D_DEFAULT):
- '''
+ """
Format duration strings.
This method is just a wrapper around isodate.isostrf.strftime and uses
P%P (D_DEFAULT) as default format.
- '''
+ """
# TODO: implement better decision for negative Durations.
# should be done in Duration class in consistent way with timedelta.
- if (((isinstance(tduration, Duration)
- and (tduration.years < 0 or tduration.months < 0
- or tduration.tdelta < timedelta(0)))
- or (isinstance(tduration, timedelta)
- and (tduration < timedelta(0))))):
- ret = '-'
+ if (
+ isinstance(tduration, Duration)
+ and (
+ tduration.years < 0
+ or tduration.months < 0
+ or tduration.tdelta < timedelta(0)
+ )
+ ) or (isinstance(tduration, timedelta) and (tduration < timedelta(0))):
+ ret = "-"
else:
- ret = ''
+ ret = ""
ret += strftime(tduration, format)
return ret
diff --git a/src/isodate/isoerror.py b/src/isodate/isoerror.py
index e7b211b..068429f 100644
--- a/src/isodate/isoerror.py
+++ b/src/isodate/isoerror.py
@@ -1,33 +1,7 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
This module defines all exception classes in the whole package.
-'''
+"""
class ISO8601Error(ValueError):
- '''Raised when the given ISO string can not be parsed.'''
+ """Raised when the given ISO string can not be parsed."""
diff --git a/src/isodate/isostrf.py b/src/isodate/isostrf.py
index 1afc810..94ce93f 100644
--- a/src/isodate/isostrf.py
+++ b/src/isodate/isostrf.py
@@ -1,29 +1,3 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
"""
This module provides an alternative strftime method.
@@ -40,152 +14,153 @@ from isodate.duration import Duration
from isodate.isotzinfo import tz_isoformat
# Date specific format strings
-DATE_BAS_COMPLETE = '%Y%m%d'
-DATE_EXT_COMPLETE = '%Y-%m-%d'
-DATE_BAS_WEEK_COMPLETE = '%YW%W%w'
-DATE_EXT_WEEK_COMPLETE = '%Y-W%W-%w'
-DATE_BAS_ORD_COMPLETE = '%Y%j'
-DATE_EXT_ORD_COMPLETE = '%Y-%j'
-DATE_BAS_WEEK = '%YW%W'
-DATE_EXT_WEEK = '%Y-W%W'
-DATE_MONTH = '%Y-%m'
-DATE_YEAR = '%Y'
-DATE_CENTURY = '%C'
+DATE_BAS_COMPLETE = "%Y%m%d"
+DATE_EXT_COMPLETE = "%Y-%m-%d"
+DATE_BAS_WEEK_COMPLETE = "%YW%W%w"
+DATE_EXT_WEEK_COMPLETE = "%Y-W%W-%w"
+DATE_BAS_ORD_COMPLETE = "%Y%j"
+DATE_EXT_ORD_COMPLETE = "%Y-%j"
+DATE_BAS_WEEK = "%YW%W"
+DATE_EXT_WEEK = "%Y-W%W"
+DATE_BAS_MONTH = "%Y%m"
+DATE_EXT_MONTH = "%Y-%m"
+DATE_YEAR = "%Y"
+DATE_CENTURY = "%C"
# Time specific format strings
-TIME_BAS_COMPLETE = '%H%M%S'
-TIME_EXT_COMPLETE = '%H:%M:%S'
-TIME_BAS_MINUTE = '%H%M'
-TIME_EXT_MINUTE = '%H:%M'
-TIME_HOUR = '%H'
+TIME_BAS_COMPLETE = "%H%M%S"
+TIME_EXT_COMPLETE = "%H:%M:%S"
+TIME_BAS_MINUTE = "%H%M"
+TIME_EXT_MINUTE = "%H:%M"
+TIME_HOUR = "%H"
# Time zone formats
-TZ_BAS = '%z'
-TZ_EXT = '%Z'
-TZ_HOUR = '%h'
+TZ_BAS = "%z"
+TZ_EXT = "%Z"
+TZ_HOUR = "%h"
# DateTime formats
-DT_EXT_COMPLETE = DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + TZ_EXT
-DT_BAS_COMPLETE = DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE + TZ_BAS
-DT_EXT_ORD_COMPLETE = DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_COMPLETE + TZ_EXT
-DT_BAS_ORD_COMPLETE = DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_COMPLETE + TZ_BAS
-DT_EXT_WEEK_COMPLETE = (DATE_EXT_WEEK_COMPLETE + 'T' +
- TIME_EXT_COMPLETE + TZ_EXT)
-DT_BAS_WEEK_COMPLETE = (DATE_BAS_WEEK_COMPLETE + 'T' +
- TIME_BAS_COMPLETE + TZ_BAS)
+DT_EXT_COMPLETE = DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT
+DT_BAS_COMPLETE = DATE_BAS_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS
+DT_EXT_ORD_COMPLETE = DATE_EXT_ORD_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT
+DT_BAS_ORD_COMPLETE = DATE_BAS_ORD_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS
+DT_EXT_WEEK_COMPLETE = DATE_EXT_WEEK_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT
+DT_BAS_WEEK_COMPLETE = DATE_BAS_WEEK_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS
# Duration formts
-D_DEFAULT = 'P%P'
-D_WEEK = 'P%p'
-D_ALT_EXT = 'P' + DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE
-D_ALT_BAS = 'P' + DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE
-D_ALT_EXT_ORD = 'P' + DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_COMPLETE
-D_ALT_BAS_ORD = 'P' + DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_COMPLETE
-
-STRF_DT_MAP = {'%d': lambda tdt, yds: '%02d' % tdt.day,
- '%f': lambda tdt, yds: '%06d' % tdt.microsecond,
- '%H': lambda tdt, yds: '%02d' % tdt.hour,
- '%j': lambda tdt, yds: '%03d' % (tdt.toordinal() -
- date(tdt.year,
- 1, 1).toordinal() +
- 1),
- '%m': lambda tdt, yds: '%02d' % tdt.month,
- '%M': lambda tdt, yds: '%02d' % tdt.minute,
- '%S': lambda tdt, yds: '%02d' % tdt.second,
- '%w': lambda tdt, yds: '%1d' % tdt.isoweekday(),
- '%W': lambda tdt, yds: '%02d' % tdt.isocalendar()[1],
- '%Y': lambda tdt, yds: (((yds != 4) and '+') or '') +
- (('%%0%dd' % yds) % tdt.year),
- '%C': lambda tdt, yds: (((yds != 4) and '+') or '') +
- (('%%0%dd' % (yds - 2)) %
- (tdt.year / 100)),
- '%h': lambda tdt, yds: tz_isoformat(tdt, '%h'),
- '%Z': lambda tdt, yds: tz_isoformat(tdt, '%Z'),
- '%z': lambda tdt, yds: tz_isoformat(tdt, '%z'),
- '%%': lambda tdt, yds: '%'}
-
-STRF_D_MAP = {'%d': lambda tdt, yds: '%02d' % tdt.days,
- '%f': lambda tdt, yds: '%06d' % tdt.microseconds,
- '%H': lambda tdt, yds: '%02d' % (tdt.seconds / 60 / 60),
- '%m': lambda tdt, yds: '%02d' % tdt.months,
- '%M': lambda tdt, yds: '%02d' % ((tdt.seconds / 60) % 60),
- '%S': lambda tdt, yds: '%02d' % (tdt.seconds % 60),
- '%W': lambda tdt, yds: '%02d' % (abs(tdt.days / 7)),
- '%Y': lambda tdt, yds: (((yds != 4) and '+') or '') +
- (('%%0%dd' % yds) % tdt.years),
- '%C': lambda tdt, yds: (((yds != 4) and '+') or '') +
- (('%%0%dd' % (yds - 2)) %
- (tdt.years / 100)),
- '%%': lambda tdt, yds: '%'}
+D_DEFAULT = "P%P"
+D_WEEK = "P%p"
+D_ALT_EXT = "P" + DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE
+D_ALT_BAS = "P" + DATE_BAS_COMPLETE + "T" + TIME_BAS_COMPLETE
+D_ALT_EXT_ORD = "P" + DATE_EXT_ORD_COMPLETE + "T" + TIME_EXT_COMPLETE
+D_ALT_BAS_ORD = "P" + DATE_BAS_ORD_COMPLETE + "T" + TIME_BAS_COMPLETE
+
+STRF_DT_MAP = {
+ "%d": lambda tdt, yds: "%02d" % tdt.day,
+ "%f": lambda tdt, yds: "%06d" % tdt.microsecond,
+ "%H": lambda tdt, yds: "%02d" % tdt.hour,
+ "%j": lambda tdt, yds: "%03d"
+ % (tdt.toordinal() - date(tdt.year, 1, 1).toordinal() + 1),
+ "%m": lambda tdt, yds: "%02d" % tdt.month,
+ "%M": lambda tdt, yds: "%02d" % tdt.minute,
+ "%S": lambda tdt, yds: "%02d" % tdt.second,
+ "%w": lambda tdt, yds: "%1d" % tdt.isoweekday(),
+ "%W": lambda tdt, yds: "%02d" % tdt.isocalendar()[1],
+ "%Y": lambda tdt, yds: (((yds != 4) and "+") or "") + (("%%0%dd" % yds) % tdt.year),
+ "%C": lambda tdt, yds: (((yds != 4) and "+") or "")
+ + (("%%0%dd" % (yds - 2)) % (tdt.year / 100)),
+ "%h": lambda tdt, yds: tz_isoformat(tdt, "%h"),
+ "%Z": lambda tdt, yds: tz_isoformat(tdt, "%Z"),
+ "%z": lambda tdt, yds: tz_isoformat(tdt, "%z"),
+ "%%": lambda tdt, yds: "%",
+}
+
+STRF_D_MAP = {
+ "%d": lambda tdt, yds: "%02d" % tdt.days,
+ "%f": lambda tdt, yds: "%06d" % tdt.microseconds,
+ "%H": lambda tdt, yds: "%02d" % (tdt.seconds / 60 / 60),
+ "%m": lambda tdt, yds: "%02d" % tdt.months,
+ "%M": lambda tdt, yds: "%02d" % ((tdt.seconds / 60) % 60),
+ "%S": lambda tdt, yds: "%02d" % (tdt.seconds % 60),
+ "%W": lambda tdt, yds: "%02d" % (abs(tdt.days / 7)),
+ "%Y": lambda tdt, yds: (((yds != 4) and "+") or "")
+ + (("%%0%dd" % yds) % tdt.years),
+ "%C": lambda tdt, yds: (((yds != 4) and "+") or "")
+ + (("%%0%dd" % (yds - 2)) % (tdt.years / 100)),
+ "%%": lambda tdt, yds: "%",
+}
def _strfduration(tdt, format, yeardigits=4):
- '''
+ """
this is the work method for timedelta and Duration instances.
see strftime for more details.
- '''
+ """
+
def repl(match):
- '''
+ """
lookup format command and return corresponding replacement.
- '''
+ """
if match.group(0) in STRF_D_MAP:
return STRF_D_MAP[match.group(0)](tdt, yeardigits)
- elif match.group(0) == '%P':
+ elif match.group(0) == "%P":
ret = []
if isinstance(tdt, Duration):
if tdt.years:
- ret.append('%sY' % abs(tdt.years))
+ ret.append("%sY" % abs(tdt.years))
if tdt.months:
- ret.append('%sM' % abs(tdt.months))
- usecs = abs((tdt.days * 24 * 60 * 60 + tdt.seconds) * 1000000 +
- tdt.microseconds)
+ ret.append("%sM" % abs(tdt.months))
+ usecs = abs(
+ (tdt.days * 24 * 60 * 60 + tdt.seconds) * 1000000 + tdt.microseconds
+ )
seconds, usecs = divmod(usecs, 1000000)
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
days, hours = divmod(hours, 24)
if days:
- ret.append('%sD' % days)
+ ret.append("%sD" % days)
if hours or minutes or seconds or usecs:
- ret.append('T')
+ ret.append("T")
if hours:
- ret.append('%sH' % hours)
+ ret.append("%sH" % hours)
if minutes:
- ret.append('%sM' % minutes)
+ ret.append("%sM" % minutes)
if seconds or usecs:
if usecs:
- ret.append(("%d.%06d" % (seconds, usecs)).rstrip('0'))
+ ret.append(("%d.%06d" % (seconds, usecs)).rstrip("0"))
else:
ret.append("%d" % seconds)
- ret.append('S')
+ ret.append("S")
# at least one component has to be there.
- return ret and ''.join(ret) or '0D'
- elif match.group(0) == '%p':
- return str(abs(tdt.days // 7)) + 'W'
+ return ret and "".join(ret) or "0D"
+ elif match.group(0) == "%p":
+ return str(abs(tdt.days // 7)) + "W"
return match.group(0)
- return re.sub('%d|%f|%H|%m|%M|%S|%W|%Y|%C|%%|%P|%p', repl,
- format)
+
+ return re.sub("%d|%f|%H|%m|%M|%S|%W|%Y|%C|%%|%P|%p", repl, format)
def _strfdt(tdt, format, yeardigits=4):
- '''
+ """
this is the work method for time and date instances.
see strftime for more details.
- '''
+ """
+
def repl(match):
- '''
+ """
lookup format command and return corresponding replacement.
- '''
+ """
if match.group(0) in STRF_DT_MAP:
return STRF_DT_MAP[match.group(0)](tdt, yeardigits)
return match.group(0)
- return re.sub('%d|%f|%H|%j|%m|%M|%S|%w|%W|%Y|%C|%z|%Z|%h|%%', repl,
- format)
+
+ return re.sub("%d|%f|%H|%j|%m|%M|%S|%w|%W|%Y|%C|%z|%Z|%h|%%", repl, format)
def strftime(tdt, format, yeardigits=4):
- '''Directive Meaning Notes
+ """Directive Meaning Notes
%d Day of the month as a decimal number [01,31].
%f Microsecond as a decimal number [0,999999], zero-padded
on the left (1)
@@ -207,7 +182,7 @@ def strftime(tdt, format, yeardigits=4):
%p ISO8601 duration format in weeks.
%% A literal '%' character.
- '''
+ """
if isinstance(tdt, (timedelta, Duration)):
return _strfduration(tdt, format, yeardigits)
return _strfdt(tdt, format, yeardigits)
diff --git a/src/isodate/isotime.py b/src/isodate/isotime.py
index 8b4d13c..3e2bb46 100644
--- a/src/isodate/isotime.py
+++ b/src/isodate/isotime.py
@@ -1,36 +1,10 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
This modules provides a method to parse an ISO 8601:2004 time string to a
Python datetime.time instance.
It supports all basic and extended formats including time zone specifications
as described in the ISO standard.
-'''
+"""
import re
from decimal import Decimal
from datetime import time
@@ -44,12 +18,12 @@ TIME_REGEX_CACHE = []
def build_time_regexps():
- '''
+ """
Build regular expressions to parse ISO time string.
The regular expressions are compiled and stored in TIME_REGEX_CACHE
for later reuse.
- '''
+ """
if not TIME_REGEX_CACHE:
# ISO 8601 time representations allow decimal fractions on least
# significant time component. Command and Full Stop are both valid
@@ -68,29 +42,35 @@ def build_time_regexps():
# +-hh =>
# isotzinfo.TZ_REGEX
def add_re(regex_text):
- TIME_REGEX_CACHE.append(
- re.compile('\A' + regex_text + TZ_REGEX + '\Z'))
+ TIME_REGEX_CACHE.append(re.compile(r"\A" + regex_text + TZ_REGEX + r"\Z"))
+
# 1. complete time:
# hh:mm:ss.ss ... extended format
- add_re(r"T?(?P<hour>[0-9]{2}):"
- r"(?P<minute>[0-9]{2}):"
- r"(?P<second>[0-9]{2}([,.][0-9]+)?)")
+ add_re(
+ r"T?(?P<hour>[0-9]{2}):"
+ r"(?P<minute>[0-9]{2}):"
+ r"(?P<second>[0-9]{2}"
+ r"([,.][0-9]+)?)"
+ )
# hhmmss.ss ... basic format
- add_re(r"T?(?P<hour>[0-9]{2})"
- r"(?P<minute>[0-9]{2})"
- r"(?P<second>[0-9]{2}([,.][0-9]+)?)")
+ add_re(
+ r"T?(?P<hour>[0-9]{2})"
+ r"(?P<minute>[0-9]{2})"
+ r"(?P<second>[0-9]{2}"
+ r"([,.][0-9]+)?)"
+ )
# 2. reduced accuracy:
# hh:mm.mm ... extended format
+ add_re(r"T?(?P<hour>[0-9]{2}):" r"(?P<minute>[0-9]{2}" r"([,.][0-9]+)?)")
# hhmm.mm ... basic format
- add_re(r"T?(?P<hour>[0-9]{2}):?"
- r"(?P<minute>[0-9]{2}([,.][0-9]+)?)")
+ add_re(r"T?(?P<hour>[0-9]{2})" r"(?P<minute>[0-9]{2}" r"([,.][0-9]+)?)")
# hh.hh ... basic format
- add_re(r"T?(?P<hour>[0-9]{2}([,.][0-9]+)?)")
+ add_re(r"T?(?P<hour>[0-9]{2}" r"([,.][0-9]+)?)")
return TIME_REGEX_CACHE
def parse_time(timestring):
- '''
+ """
Parses ISO 8601 times into datetime.time objects.
Following ISO 8601 formats are supported:
@@ -106,7 +86,7 @@ def parse_time(timestring):
+-hhmm basic hours and minutes
+-hh:mm extended hours and minutes
+-hh hours
- '''
+ """
isotimes = build_time_regexps()
for pattern in isotimes:
match = pattern.match(timestring)
@@ -114,41 +94,58 @@ def parse_time(timestring):
groups = match.groupdict()
for key, value in groups.items():
if value is not None:
- groups[key] = value.replace(',', '.')
- tzinfo = build_tzinfo(groups['tzname'], groups['tzsign'],
- int(groups['tzhour'] or 0),
- int(groups['tzmin'] or 0))
- if 'second' in groups:
+ groups[key] = value.replace(",", ".")
+ tzinfo = build_tzinfo(
+ groups["tzname"],
+ groups["tzsign"],
+ int(groups["tzhour"] or 0),
+ int(groups["tzmin"] or 0),
+ )
+ if "second" in groups:
# round to microseconds if fractional seconds are more precise
- second = Decimal(groups['second']).quantize(Decimal('.000001'))
- microsecond = (second - int(second)) * long(1e6)
+ second = Decimal(groups["second"]).quantize(Decimal(".000001"))
+ microsecond = (second - int(second)) * int(1e6)
# int(...) ... no rounding
# to_integral() ... rounding
- return time(int(groups['hour']), int(groups['minute']),
- int(second), int(microsecond.to_integral()),
- tzinfo)
- if 'minute' in groups:
- minute = Decimal(groups['minute'])
+ return time(
+ int(groups["hour"]),
+ int(groups["minute"]),
+ int(second),
+ int(microsecond.to_integral()),
+ tzinfo,
+ )
+ if "minute" in groups:
+ minute = Decimal(groups["minute"])
second = (minute - int(minute)) * 60
- microsecond = (second - int(second)) * long(1e6)
- return time(int(groups['hour']), int(minute), int(second),
- int(microsecond.to_integral()), tzinfo)
+ microsecond = (second - int(second)) * int(1e6)
+ return time(
+ int(groups["hour"]),
+ int(minute),
+ int(second),
+ int(microsecond.to_integral()),
+ tzinfo,
+ )
else:
microsecond, second, minute = 0, 0, 0
- hour = Decimal(groups['hour'])
+ hour = Decimal(groups["hour"])
minute = (hour - int(hour)) * 60
second = (minute - int(minute)) * 60
- microsecond = (second - int(second)) * long(1e6)
- return time(int(hour), int(minute), int(second),
- int(microsecond.to_integral()), tzinfo)
- raise ISO8601Error('Unrecognised ISO 8601 time format: %r' % timestring)
+ microsecond = (second - int(second)) * int(1e6)
+ return time(
+ int(hour),
+ int(minute),
+ int(second),
+ int(microsecond.to_integral()),
+ tzinfo,
+ )
+ raise ISO8601Error("Unrecognised ISO 8601 time format: %r" % timestring)
def time_isoformat(ttime, format=TIME_EXT_COMPLETE + TZ_EXT):
- '''
+ """
Format time strings.
This method is just a wrapper around isodate.isostrf.strftime and uses
Time-Extended-Complete with extended time zone as default format.
- '''
+ """
return strftime(ttime, format)
diff --git a/src/isodate/isotzinfo.py b/src/isodate/isotzinfo.py
index 263afd7..b7fa41b 100644
--- a/src/isodate/isotzinfo.py
+++ b/src/isodate/isotzinfo.py
@@ -1,64 +1,39 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
This module provides an ISO 8601:2004 time zone info parser.
It offers a function to parse the time zone offset as specified by ISO 8601.
-'''
+"""
import re
from isodate.isoerror import ISO8601Error
from isodate.tzinfo import UTC, FixedOffset, ZERO
-TZ_REGEX = r"(?P<tzname>(Z|(?P<tzsign>[+-])"\
- r"(?P<tzhour>[0-9]{2})(:(?P<tzmin>[0-9]{2}))?)?)"
+TZ_REGEX = (
+ r"(?P<tzname>(Z|(?P<tzsign>[+-])" r"(?P<tzhour>[0-9]{2})(:?(?P<tzmin>[0-9]{2}))?)?)"
+)
TZ_RE = re.compile(TZ_REGEX)
-def build_tzinfo(tzname, tzsign='+', tzhour=0, tzmin=0):
- '''
+def build_tzinfo(tzname, tzsign="+", tzhour=0, tzmin=0):
+ """
create a tzinfo instance according to given parameters.
tzname:
'Z' ... return UTC
'' | None ... return None
other ... return FixedOffset
- '''
- if tzname is None or tzname == '':
+ """
+ if tzname is None or tzname == "":
return None
- if tzname == 'Z':
+ if tzname == "Z":
return UTC
- tzsign = ((tzsign == '-') and -1) or 1
+ tzsign = ((tzsign == "-") and -1) or 1
return FixedOffset(tzsign * tzhour, tzsign * tzmin, tzname)
def parse_tzinfo(tzstring):
- '''
+ """
Parses ISO 8601 time zone designators to tzinfo objecs.
A time zone designator can be in the following format:
@@ -67,18 +42,21 @@ def parse_tzinfo(tzstring):
+-hhmm basic hours and minutes
+-hh:mm extended hours and minutes
+-hh hours
- '''
+ """
match = TZ_RE.match(tzstring)
if match:
groups = match.groupdict()
- return build_tzinfo(groups['tzname'], groups['tzsign'],
- int(groups['tzhour'] or 0),
- int(groups['tzmin'] or 0))
- raise ISO8601Error('%s not a valid time zone info' % tzstring)
+ return build_tzinfo(
+ groups["tzname"],
+ groups["tzsign"],
+ int(groups["tzhour"] or 0),
+ int(groups["tzmin"] or 0),
+ )
+ raise ISO8601Error("%s not a valid time zone info" % tzstring)
-def tz_isoformat(dt, format='%Z'):
- '''
+def tz_isoformat(dt, format="%Z"):
+ """
return time zone offset ISO 8601 formatted.
The various ISO formats can be chosen with the format parameter.
@@ -89,24 +67,24 @@ def tz_isoformat(dt, format='%Z'):
%h ... +-HH
%z ... +-HHMM
%Z ... +-HH:MM
- '''
+ """
tzinfo = dt.tzinfo
if (tzinfo is None) or (tzinfo.utcoffset(dt) is None):
- return ''
+ return ""
if tzinfo.utcoffset(dt) == ZERO and tzinfo.dst(dt) == ZERO:
- return 'Z'
+ return "Z"
tdelta = tzinfo.utcoffset(dt)
seconds = tdelta.days * 24 * 60 * 60 + tdelta.seconds
- sign = ((seconds < 0) and '-') or '+'
+ sign = ((seconds < 0) and "-") or "+"
seconds = abs(seconds)
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
if hours > 99:
- raise OverflowError('can not handle differences > 99 hours')
- if format == '%Z':
- return '%s%02d:%02d' % (sign, hours, minutes)
- elif format == '%z':
- return '%s%02d%02d' % (sign, hours, minutes)
- elif format == '%h':
- return '%s%02d' % (sign, hours)
+ raise OverflowError("can not handle differences > 99 hours")
+ if format == "%Z":
+ return "%s%02d:%02d" % (sign, hours, minutes)
+ elif format == "%z":
+ return "%s%02d%02d" % (sign, hours, minutes)
+ elif format == "%h":
+ return "%s%02d" % (sign, hours)
raise ValueError('unknown format string "%s"' % format)
diff --git a/src/isodate/tests/__init__.py b/src/isodate/tests/__init__.py
index 09dba2e..ea0f433 100644
--- a/src/isodate/tests/__init__.py
+++ b/src/isodate/tests/__init__.py
@@ -1,50 +1,36 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
Collect all test suites into one TestSuite instance.
-'''
+"""
import unittest
-from isodate.tests import (test_date, test_time, test_datetime, test_duration,
- test_strf, test_pickle)
+import warnings
+from isodate.tests import (
+ test_date,
+ test_time,
+ test_datetime,
+ test_duration,
+ test_strf,
+ test_pickle,
+)
def test_suite():
- '''
+ """
Return a new TestSuite instance consisting of all available TestSuites.
- '''
- return unittest.TestSuite([
- test_date.test_suite(),
- test_time.test_suite(),
- test_datetime.test_suite(),
- test_duration.test_suite(),
- test_strf.test_suite(),
- test_pickle.test_suite(),
- ])
+ """
+ warnings.filterwarnings("error", module=r"isodate(\..)*")
-if __name__ == '__main__':
- unittest.main(defaultTest='test_suite')
+ return unittest.TestSuite(
+ [
+ test_date.test_suite(),
+ test_time.test_suite(),
+ test_datetime.test_suite(),
+ test_duration.test_suite(),
+ test_strf.test_suite(),
+ test_pickle.test_suite(),
+ ]
+ )
+
+
+if __name__ == "__main__":
+ unittest.main(defaultTest="test_suite")
diff --git a/src/isodate/tests/test_date.py b/src/isodate/tests/test_date.py
index fdc1043..cee3903 100644
--- a/src/isodate/tests/test_date.py
+++ b/src/isodate/tests/test_date.py
@@ -1,36 +1,11 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
Test cases for the isodate module.
-'''
+"""
import unittest
from datetime import date
from isodate import parse_date, ISO8601Error, date_isoformat
-from isodate import DATE_CENTURY, DATE_YEAR, DATE_MONTH
+from isodate import DATE_CENTURY, DATE_YEAR
+from isodate import DATE_BAS_MONTH, DATE_EXT_MONTH
from isodate import DATE_EXT_COMPLETE, DATE_BAS_COMPLETE
from isodate import DATE_BAS_ORD_COMPLETE, DATE_EXT_ORD_COMPLETE
from isodate import DATE_BAS_WEEK, DATE_BAS_WEEK_COMPLETE
@@ -40,84 +15,89 @@ from isodate import DATE_EXT_WEEK, DATE_EXT_WEEK_COMPLETE
# result from the parse_date method. A result of None means an ISO8601Error
# is expected. The test cases are grouped into dates with 4 digit years
# and 6 digit years.
-TEST_CASES = {4: [('19', date(1901, 1, 1), DATE_CENTURY),
- ('1985', date(1985, 1, 1), DATE_YEAR),
- ('1985-04', date(1985, 4, 1), DATE_MONTH),
- ('1985-04-12', date(1985, 4, 12), DATE_EXT_COMPLETE),
- ('19850412', date(1985, 4, 12), DATE_BAS_COMPLETE),
- ('1985102', date(1985, 4, 12), DATE_BAS_ORD_COMPLETE),
- ('1985-102', date(1985, 4, 12), DATE_EXT_ORD_COMPLETE),
- ('1985W155', date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE),
- ('1985-W15-5', date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE),
- ('1985W15', date(1985, 4, 8), DATE_BAS_WEEK),
- ('1985-W15', date(1985, 4, 8), DATE_EXT_WEEK),
- ('1989-W15', date(1989, 4, 10), DATE_EXT_WEEK),
- ('1989-W15-5', date(1989, 4, 14), DATE_EXT_WEEK_COMPLETE),
- ('1-W1-1', None, DATE_BAS_WEEK_COMPLETE)],
- 6: [('+0019', date(1901, 1, 1), DATE_CENTURY),
- ('+001985', date(1985, 1, 1), DATE_YEAR),
- ('+001985-04', date(1985, 4, 1), DATE_MONTH),
- ('+001985-04-12', date(1985, 4, 12), DATE_EXT_COMPLETE),
- ('+0019850412', date(1985, 4, 12), DATE_BAS_COMPLETE),
- ('+001985102', date(1985, 4, 12), DATE_BAS_ORD_COMPLETE),
- ('+001985-102', date(1985, 4, 12), DATE_EXT_ORD_COMPLETE),
- ('+001985W155', date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE),
- ('+001985-W15-5', date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE),
- ('+001985W15', date(1985, 4, 8), DATE_BAS_WEEK),
- ('+001985-W15', date(1985, 4, 8), DATE_EXT_WEEK)]}
+TEST_CASES = {
+ 4: [
+ ("19", date(1901, 1, 1), DATE_CENTURY),
+ ("1985", date(1985, 1, 1), DATE_YEAR),
+ ("1985-04", date(1985, 4, 1), DATE_EXT_MONTH),
+ ("198504", date(1985, 4, 1), DATE_BAS_MONTH),
+ ("1985-04-12", date(1985, 4, 12), DATE_EXT_COMPLETE),
+ ("19850412", date(1985, 4, 12), DATE_BAS_COMPLETE),
+ ("1985102", date(1985, 4, 12), DATE_BAS_ORD_COMPLETE),
+ ("1985-102", date(1985, 4, 12), DATE_EXT_ORD_COMPLETE),
+ ("1985W155", date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE),
+ ("1985-W15-5", date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE),
+ ("1985W15", date(1985, 4, 8), DATE_BAS_WEEK),
+ ("1985-W15", date(1985, 4, 8), DATE_EXT_WEEK),
+ ("1989-W15", date(1989, 4, 10), DATE_EXT_WEEK),
+ ("1989-W15-5", date(1989, 4, 14), DATE_EXT_WEEK_COMPLETE),
+ ("1-W1-1", None, DATE_BAS_WEEK_COMPLETE),
+ ],
+ 6: [
+ ("+0019", date(1901, 1, 1), DATE_CENTURY),
+ ("+001985", date(1985, 1, 1), DATE_YEAR),
+ ("+001985-04", date(1985, 4, 1), DATE_EXT_MONTH),
+ ("+001985-04-12", date(1985, 4, 12), DATE_EXT_COMPLETE),
+ ("+0019850412", date(1985, 4, 12), DATE_BAS_COMPLETE),
+ ("+001985102", date(1985, 4, 12), DATE_BAS_ORD_COMPLETE),
+ ("+001985-102", date(1985, 4, 12), DATE_EXT_ORD_COMPLETE),
+ ("+001985W155", date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE),
+ ("+001985-W15-5", date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE),
+ ("+001985W15", date(1985, 4, 8), DATE_BAS_WEEK),
+ ("+001985-W15", date(1985, 4, 8), DATE_EXT_WEEK),
+ ],
+}
def create_testcase(yeardigits, datestring, expectation, format):
- '''
+ """
Create a TestCase class for a specific test.
This allows having a separate TestCase for each test tuple from the
TEST_CASES list, so that a failed test won't stop other tests.
- '''
+ """
class TestDate(unittest.TestCase):
- '''
+ """
A test case template to parse an ISO date string into a date
object.
- '''
+ """
def test_parse(self):
- '''
+ """
Parse an ISO date string and compare it to the expected value.
- '''
+ """
if expectation is None:
- self.assertRaises(ISO8601Error, parse_date, datestring,
- yeardigits)
+ self.assertRaises(ISO8601Error, parse_date, datestring, yeardigits)
else:
result = parse_date(datestring, yeardigits)
self.assertEqual(result, expectation)
def test_format(self):
- '''
+ """
Take date object and create ISO string from it.
This is the reverse test to test_parse.
- '''
+ """
if expectation is None:
- self.assertRaises(AttributeError,
- date_isoformat, expectation, format,
- yeardigits)
+ self.assertRaises(
+ AttributeError, date_isoformat, expectation, format, yeardigits
+ )
else:
- self.assertEqual(date_isoformat(expectation, format,
- yeardigits),
- datestring)
+ self.assertEqual(
+ date_isoformat(expectation, format, yeardigits), datestring
+ )
return unittest.TestLoader().loadTestsFromTestCase(TestDate)
def test_suite():
- '''
+ """
Construct a TestSuite instance for all test cases.
- '''
+ """
suite = unittest.TestSuite()
for yeardigits, tests in TEST_CASES.items():
for datestring, expectation, format in tests:
- suite.addTest(create_testcase(yeardigits, datestring,
- expectation, format))
+ suite.addTest(create_testcase(yeardigits, datestring, expectation, format))
return suite
@@ -125,5 +105,6 @@ def test_suite():
def load_tests(loader, tests, pattern):
return test_suite()
-if __name__ == '__main__':
- unittest.main(defaultTest='test_suite')
+
+if __name__ == "__main__":
+ unittest.main(defaultTest="test_suite")
diff --git a/src/isodate/tests/test_datetime.py b/src/isodate/tests/test_datetime.py
index ddad5da..465654e 100644
--- a/src/isodate/tests/test_datetime.py
+++ b/src/isodate/tests/test_datetime.py
@@ -1,32 +1,6 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
Test cases for the isodatetime module.
-'''
+"""
import unittest
from datetime import datetime
@@ -41,52 +15,113 @@ from isodate import DATE_BAS_WEEK_COMPLETE, DATE_EXT_WEEK_COMPLETE
# the following list contains tuples of ISO datetime strings and the expected
# result from the parse_datetime method. A result of None means an ISO8601Error
# is expected.
-TEST_CASES = [('19850412T1015', datetime(1985, 4, 12, 10, 15),
- DATE_BAS_COMPLETE + 'T' + TIME_BAS_MINUTE,
- '19850412T1015'),
- ('1985-04-12T10:15', datetime(1985, 4, 12, 10, 15),
- DATE_EXT_COMPLETE + 'T' + TIME_EXT_MINUTE,
- '1985-04-12T10:15'),
- ('1985102T1015Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC),
- DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS,
- '1985102T1015Z'),
- ('1985-102T10:15Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC),
- DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_EXT,
- '1985-102T10:15Z'),
- ('1985W155T1015+0400', datetime(1985, 4, 12, 10, 15,
- tzinfo=FixedOffset(4, 0,
- '+0400')),
- DATE_BAS_WEEK_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS,
- '1985W155T1015+0400'),
- ('1985-W15-5T10:15+04', datetime(1985, 4, 12, 10, 15,
- tzinfo=FixedOffset(4, 0,
- '+0400'),),
- DATE_EXT_WEEK_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_HOUR,
- '1985-W15-5T10:15+04'),
- ('20110410T101225.123000Z',
- datetime(2011, 4, 10, 10, 12, 25, 123000, tzinfo=UTC),
- DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE + ".%f" + TZ_BAS,
- '20110410T101225.123000Z'),
- ('2012-10-12T08:29:46.069178Z',
- datetime(2012, 10, 12, 8, 29, 46, 69178, tzinfo=UTC),
- DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
- '2012-10-12T08:29:46.069178Z'),
- ('2012-10-12T08:29:46.691780Z',
- datetime(2012, 10, 12, 8, 29, 46, 691780, tzinfo=UTC),
- DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
- '2012-10-12T08:29:46.691780Z'),
- ('2012-10-30T08:55:22.1234567Z',
- datetime(2012, 10, 30, 8, 55, 22, 123457, tzinfo=UTC),
- DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
- '2012-10-30T08:55:22.123457Z'),
- ('2012-10-30T08:55:22.1234561Z',
- datetime(2012, 10, 30, 8, 55, 22, 123456, tzinfo=UTC),
- DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
- '2012-10-30T08:55:22.123456Z'),
- ('2014-08-18 14:55:22.123456Z', None,
- DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
- '2014-08-18T14:55:22.123456Z'),
- ]
+TEST_CASES = [
+ (
+ "19850412T1015",
+ datetime(1985, 4, 12, 10, 15),
+ DATE_BAS_COMPLETE + "T" + TIME_BAS_MINUTE,
+ "19850412T1015",
+ ),
+ (
+ "1985-04-12T10:15",
+ datetime(1985, 4, 12, 10, 15),
+ DATE_EXT_COMPLETE + "T" + TIME_EXT_MINUTE,
+ "1985-04-12T10:15",
+ ),
+ (
+ "1985102T1015Z",
+ datetime(1985, 4, 12, 10, 15, tzinfo=UTC),
+ DATE_BAS_ORD_COMPLETE + "T" + TIME_BAS_MINUTE + TZ_BAS,
+ "1985102T1015Z",
+ ),
+ (
+ "1985-102T10:15Z",
+ datetime(1985, 4, 12, 10, 15, tzinfo=UTC),
+ DATE_EXT_ORD_COMPLETE + "T" + TIME_EXT_MINUTE + TZ_EXT,
+ "1985-102T10:15Z",
+ ),
+ (
+ "1985W155T1015+0400",
+ datetime(1985, 4, 12, 10, 15, tzinfo=FixedOffset(4, 0, "+0400")),
+ DATE_BAS_WEEK_COMPLETE + "T" + TIME_BAS_MINUTE + TZ_BAS,
+ "1985W155T1015+0400",
+ ),
+ (
+ "1985-W15-5T10:15+04",
+ datetime(
+ 1985,
+ 4,
+ 12,
+ 10,
+ 15,
+ tzinfo=FixedOffset(4, 0, "+0400"),
+ ),
+ DATE_EXT_WEEK_COMPLETE + "T" + TIME_EXT_MINUTE + TZ_HOUR,
+ "1985-W15-5T10:15+04",
+ ),
+ (
+ "1985-W15-5T10:15-0430",
+ datetime(
+ 1985,
+ 4,
+ 12,
+ 10,
+ 15,
+ tzinfo=FixedOffset(-4, -30, "-0430"),
+ ),
+ DATE_EXT_WEEK_COMPLETE + "T" + TIME_EXT_MINUTE + TZ_BAS,
+ "1985-W15-5T10:15-0430",
+ ),
+ (
+ "1985-W15-5T10:15+04:45",
+ datetime(
+ 1985,
+ 4,
+ 12,
+ 10,
+ 15,
+ tzinfo=FixedOffset(4, 45, "+04:45"),
+ ),
+ DATE_EXT_WEEK_COMPLETE + "T" + TIME_EXT_MINUTE + TZ_EXT,
+ "1985-W15-5T10:15+04:45",
+ ),
+ (
+ "20110410T101225.123000Z",
+ datetime(2011, 4, 10, 10, 12, 25, 123000, tzinfo=UTC),
+ DATE_BAS_COMPLETE + "T" + TIME_BAS_COMPLETE + ".%f" + TZ_BAS,
+ "20110410T101225.123000Z",
+ ),
+ (
+ "2012-10-12T08:29:46.069178Z",
+ datetime(2012, 10, 12, 8, 29, 46, 69178, tzinfo=UTC),
+ DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + ".%f" + TZ_BAS,
+ "2012-10-12T08:29:46.069178Z",
+ ),
+ (
+ "2012-10-12T08:29:46.691780Z",
+ datetime(2012, 10, 12, 8, 29, 46, 691780, tzinfo=UTC),
+ DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + ".%f" + TZ_BAS,
+ "2012-10-12T08:29:46.691780Z",
+ ),
+ (
+ "2012-10-30T08:55:22.1234567Z",
+ datetime(2012, 10, 30, 8, 55, 22, 123457, tzinfo=UTC),
+ DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + ".%f" + TZ_BAS,
+ "2012-10-30T08:55:22.123457Z",
+ ),
+ (
+ "2012-10-30T08:55:22.1234561Z",
+ datetime(2012, 10, 30, 8, 55, 22, 123456, tzinfo=UTC),
+ DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + ".%f" + TZ_BAS,
+ "2012-10-30T08:55:22.123456Z",
+ ),
+ (
+ "2014-08-18 14:55:22.123456Z",
+ None,
+ DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + ".%f" + TZ_BAS,
+ "2014-08-18T14:55:22.123456Z",
+ ),
+]
def create_testcase(datetimestring, expectation, format, output):
@@ -98,43 +133,42 @@ def create_testcase(datetimestring, expectation, format, output):
"""
class TestDateTime(unittest.TestCase):
- '''
+ """
A test case template to parse an ISO datetime string into a
datetime object.
- '''
+ """
def test_parse(self):
- '''
+ """
Parse an ISO datetime string and compare it to the expected value.
- '''
+ """
if expectation is None:
self.assertRaises(ISO8601Error, parse_datetime, datetimestring)
else:
self.assertEqual(parse_datetime(datetimestring), expectation)
def test_format(self):
- '''
+ """
Take datetime object and create ISO string from it.
This is the reverse test to test_parse.
- '''
+ """
if expectation is None:
- self.assertRaises(AttributeError,
- datetime_isoformat, expectation, format)
+ self.assertRaises(
+ AttributeError, datetime_isoformat, expectation, format
+ )
else:
- self.assertEqual(datetime_isoformat(expectation, format),
- output)
+ self.assertEqual(datetime_isoformat(expectation, format), output)
return unittest.TestLoader().loadTestsFromTestCase(TestDateTime)
def test_suite():
- '''
+ """
Construct a TestSuite instance for all test cases.
- '''
+ """
suite = unittest.TestSuite()
for datetimestring, expectation, format, output in TEST_CASES:
- suite.addTest(create_testcase(datetimestring, expectation,
- format, output))
+ suite.addTest(create_testcase(datetimestring, expectation, format, output))
return suite
@@ -142,5 +176,6 @@ def test_suite():
def load_tests(loader, tests, pattern):
return test_suite()
-if __name__ == '__main__':
- unittest.main(defaultTest='test_suite')
+
+if __name__ == "__main__":
+ unittest.main(defaultTest="test_suite")
diff --git a/src/isodate/tests/test_duration.py b/src/isodate/tests/test_duration.py
index 6cf6b65..5d36a3e 100644
--- a/src/isodate/tests/test_duration.py
+++ b/src/isodate/tests/test_duration.py
@@ -1,32 +1,6 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
Test cases for the isoduration module.
-'''
+"""
import unittest
import operator
from datetime import timedelta, date, datetime
@@ -37,47 +11,40 @@ from isodate import D_DEFAULT, D_WEEK, D_ALT_EXT, duration_isoformat
# the following list contains tuples of ISO duration strings and the expected
# result from the parse_duration method. A result of None means an ISO8601Error
# is expected.
-PARSE_TEST_CASES = {'P18Y9M4DT11H9M8S': (Duration(4, 8, 0, 0, 9, 11, 0, 9, 18),
- D_DEFAULT, None),
- 'P2W': (timedelta(weeks=2), D_WEEK, None),
- 'P3Y6M4DT12H30M5S': (Duration(4, 5, 0, 0, 30, 12, 0, 6, 3),
- D_DEFAULT, None),
- 'P23DT23H': (timedelta(hours=23, days=23),
- D_DEFAULT, None),
- 'P4Y': (Duration(years=4), D_DEFAULT, None),
- 'P1M': (Duration(months=1), D_DEFAULT, None),
- 'PT1M': (timedelta(minutes=1), D_DEFAULT, None),
- 'P0.5Y': (Duration(years=0.5), D_DEFAULT, None),
- 'PT36H': (timedelta(hours=36), D_DEFAULT, 'P1DT12H'),
- 'P1DT12H': (timedelta(days=1, hours=12), D_DEFAULT, None),
- '+P11D': (timedelta(days=11), D_DEFAULT, 'P11D'),
- '-P2W': (timedelta(weeks=-2), D_WEEK, None),
- '-P2.2W': (timedelta(weeks=-2.2), D_DEFAULT,
- '-P15DT9H36M'),
- 'P1DT2H3M4S': (timedelta(days=1, hours=2, minutes=3,
- seconds=4), D_DEFAULT, None),
- 'P1DT2H3M': (timedelta(days=1, hours=2, minutes=3),
- D_DEFAULT, None),
- 'P1DT2H': (timedelta(days=1, hours=2), D_DEFAULT, None),
- 'PT2H': (timedelta(hours=2), D_DEFAULT, None),
- 'PT2.3H': (timedelta(hours=2.3), D_DEFAULT, 'PT2H18M'),
- 'PT2H3M4S': (timedelta(hours=2, minutes=3, seconds=4),
- D_DEFAULT, None),
- 'PT3M4S': (timedelta(minutes=3, seconds=4), D_DEFAULT,
- None),
- 'PT22S': (timedelta(seconds=22), D_DEFAULT, None),
- 'PT22.22S': (timedelta(seconds=22.22), 'PT%S.%fS',
- 'PT22.220000S'),
- '-P2Y': (Duration(years=-2), D_DEFAULT, None),
- '-P3Y6M4DT12H30M5S': (Duration(-4, -5, 0, 0, -30, -12, 0,
- -6, -3), D_DEFAULT, None),
- '-P1DT2H3M4S': (timedelta(days=-1, hours=-2, minutes=-3,
- seconds=-4), D_DEFAULT, None),
- # alternative format
- 'P0018-09-04T11:09:08': (Duration(4, 8, 0, 0, 9, 11, 0, 9,
- 18), D_ALT_EXT, None),
- # 'PT000022.22': timedelta(seconds=22.22),
- }
+PARSE_TEST_CASES = {
+ "P18Y9M4DT11H9M8S": (Duration(4, 8, 0, 0, 9, 11, 0, 9, 18), D_DEFAULT, None),
+ "P2W": (timedelta(weeks=2), D_WEEK, None),
+ "P3Y6M4DT12H30M5S": (Duration(4, 5, 0, 0, 30, 12, 0, 6, 3), D_DEFAULT, None),
+ "P23DT23H": (timedelta(hours=23, days=23), D_DEFAULT, None),
+ "P4Y": (Duration(years=4), D_DEFAULT, None),
+ "P1M": (Duration(months=1), D_DEFAULT, None),
+ "PT1M": (timedelta(minutes=1), D_DEFAULT, None),
+ "P0.5Y": (Duration(years=0.5), D_DEFAULT, None),
+ "PT36H": (timedelta(hours=36), D_DEFAULT, "P1DT12H"),
+ "P1DT12H": (timedelta(days=1, hours=12), D_DEFAULT, None),
+ "+P11D": (timedelta(days=11), D_DEFAULT, "P11D"),
+ "-P2W": (timedelta(weeks=-2), D_WEEK, None),
+ "-P2.2W": (timedelta(weeks=-2.2), D_DEFAULT, "-P15DT9H36M"),
+ "P1DT2H3M4S": (timedelta(days=1, hours=2, minutes=3, seconds=4), D_DEFAULT, None),
+ "P1DT2H3M": (timedelta(days=1, hours=2, minutes=3), D_DEFAULT, None),
+ "P1DT2H": (timedelta(days=1, hours=2), D_DEFAULT, None),
+ "PT2H": (timedelta(hours=2), D_DEFAULT, None),
+ "PT2.3H": (timedelta(hours=2.3), D_DEFAULT, "PT2H18M"),
+ "PT2H3M4S": (timedelta(hours=2, minutes=3, seconds=4), D_DEFAULT, None),
+ "PT3M4S": (timedelta(minutes=3, seconds=4), D_DEFAULT, None),
+ "PT22S": (timedelta(seconds=22), D_DEFAULT, None),
+ "PT22.22S": (timedelta(seconds=22.22), "PT%S.%fS", "PT22.220000S"),
+ "-P2Y": (Duration(years=-2), D_DEFAULT, None),
+ "-P3Y6M4DT12H30M5S": (Duration(-4, -5, 0, 0, -30, -12, 0, -6, -3), D_DEFAULT, None),
+ "-P1DT2H3M4S": (
+ timedelta(days=-1, hours=-2, minutes=-3, seconds=-4),
+ D_DEFAULT,
+ None,
+ ),
+ # alternative format
+ "P0018-09-04T11:09:08": (Duration(4, 8, 0, 0, 9, 11, 0, 9, 18), D_ALT_EXT, None),
+ # 'PT000022.22': timedelta(seconds=22.22),
+}
# d1 d2 '+', '-', '>'
# A list of test cases to test addition and subtraction between datetime and
@@ -85,163 +52,174 @@ PARSE_TEST_CASES = {'P18Y9M4DT11H9M8S': (Duration(4, 8, 0, 0, 9, 11, 0, 9, 18),
# each tuple contains 2 duration strings, and a result string for addition and
# one for subtraction. The last value says, if the first duration is greater
# than the second.
-MATH_TEST_CASES = (('P5Y7M1DT9H45M16.72S', 'PT27M24.68S',
- 'P5Y7M1DT10H12M41.4S', 'P5Y7M1DT9H17M52.04S', None),
- ('PT28M12.73S', 'PT56M29.92S',
- 'PT1H24M42.65S', '-PT28M17.19S', False),
- ('P3Y7M23DT5H25M0.33S', 'PT1H1.95S',
- 'P3Y7M23DT6H25M2.28S', 'P3Y7M23DT4H24M58.38S', None),
- ('PT1H1.95S', 'P3Y7M23DT5H25M0.33S',
- 'P3Y7M23DT6H25M2.28S', '-P3Y7M23DT4H24M58.38S', None),
- ('P1332DT55M0.33S', 'PT1H1.95S',
- 'P1332DT1H55M2.28S', 'P1331DT23H54M58.38S', True),
- ('PT1H1.95S', 'P1332DT55M0.33S',
- 'P1332DT1H55M2.28S', '-P1331DT23H54M58.38S', False))
+MATH_TEST_CASES = (
+ (
+ "P5Y7M1DT9H45M16.72S",
+ "PT27M24.68S",
+ "P5Y7M1DT10H12M41.4S",
+ "P5Y7M1DT9H17M52.04S",
+ None,
+ ),
+ ("PT28M12.73S", "PT56M29.92S", "PT1H24M42.65S", "-PT28M17.19S", False),
+ (
+ "P3Y7M23DT5H25M0.33S",
+ "PT1H1.95S",
+ "P3Y7M23DT6H25M2.28S",
+ "P3Y7M23DT4H24M58.38S",
+ None,
+ ),
+ (
+ "PT1H1.95S",
+ "P3Y7M23DT5H25M0.33S",
+ "P3Y7M23DT6H25M2.28S",
+ "-P3Y7M23DT4H24M58.38S",
+ None,
+ ),
+ ("P1332DT55M0.33S", "PT1H1.95S", "P1332DT1H55M2.28S", "P1331DT23H54M58.38S", True),
+ (
+ "PT1H1.95S",
+ "P1332DT55M0.33S",
+ "P1332DT1H55M2.28S",
+ "-P1331DT23H54M58.38S",
+ False,
+ ),
+)
# A list of test cases to test addition and subtraction of date/datetime
# and Duration objects. They are tested against the results of an
# equal long timedelta duration.
-DATE_TEST_CASES = ((date(2008, 2, 29),
- timedelta(days=10, hours=12, minutes=20),
- Duration(days=10, hours=12, minutes=20)),
- (date(2008, 1, 31),
- timedelta(days=10, hours=12, minutes=20),
- Duration(days=10, hours=12, minutes=20)),
- (datetime(2008, 2, 29),
- timedelta(days=10, hours=12, minutes=20),
- Duration(days=10, hours=12, minutes=20)),
- (datetime(2008, 1, 31),
- timedelta(days=10, hours=12, minutes=20),
- Duration(days=10, hours=12, minutes=20)),
- (datetime(2008, 4, 21),
- timedelta(days=10, hours=12, minutes=20),
- Duration(days=10, hours=12, minutes=20)),
- (datetime(2008, 5, 5),
- timedelta(days=10, hours=12, minutes=20),
- Duration(days=10, hours=12, minutes=20)),
- (datetime(2000, 1, 1),
- timedelta(hours=-33),
- Duration(hours=-33)),
- (datetime(2008, 5, 5),
- Duration(years=1, months=1, days=10, hours=12,
- minutes=20),
- Duration(months=13, days=10, hours=12, minutes=20)),
- (datetime(2000, 3, 30),
- Duration(years=1, months=1, days=10, hours=12,
- minutes=20),
- Duration(months=13, days=10, hours=12, minutes=20)),
- )
+DATE_TEST_CASES = (
+ (
+ date(2008, 2, 29),
+ timedelta(days=10, hours=12, minutes=20),
+ Duration(days=10, hours=12, minutes=20),
+ ),
+ (
+ date(2008, 1, 31),
+ timedelta(days=10, hours=12, minutes=20),
+ Duration(days=10, hours=12, minutes=20),
+ ),
+ (
+ datetime(2008, 2, 29),
+ timedelta(days=10, hours=12, minutes=20),
+ Duration(days=10, hours=12, minutes=20),
+ ),
+ (
+ datetime(2008, 1, 31),
+ timedelta(days=10, hours=12, minutes=20),
+ Duration(days=10, hours=12, minutes=20),
+ ),
+ (
+ datetime(2008, 4, 21),
+ timedelta(days=10, hours=12, minutes=20),
+ Duration(days=10, hours=12, minutes=20),
+ ),
+ (
+ datetime(2008, 5, 5),
+ timedelta(days=10, hours=12, minutes=20),
+ Duration(days=10, hours=12, minutes=20),
+ ),
+ (datetime(2000, 1, 1), timedelta(hours=-33), Duration(hours=-33)),
+ (
+ datetime(2008, 5, 5),
+ Duration(years=1, months=1, days=10, hours=12, minutes=20),
+ Duration(months=13, days=10, hours=12, minutes=20),
+ ),
+ (
+ datetime(2000, 3, 30),
+ Duration(years=1, months=1, days=10, hours=12, minutes=20),
+ Duration(months=13, days=10, hours=12, minutes=20),
+ ),
+)
# A list of test cases of additon of date/datetime and Duration. The results
# are compared against a given expected result.
DATE_CALC_TEST_CASES = (
- (date(2000, 2, 1),
- Duration(years=1, months=1),
- date(2001, 3, 1)),
- (date(2000, 2, 29),
- Duration(years=1, months=1),
- date(2001, 3, 29)),
- (date(2000, 2, 29),
- Duration(years=1),
- date(2001, 2, 28)),
- (date(1996, 2, 29),
- Duration(years=4),
- date(2000, 2, 29)),
- (date(2096, 2, 29),
- Duration(years=4),
- date(2100, 2, 28)),
- (date(2000, 2, 1),
- Duration(years=-1, months=-1),
- date(1999, 1, 1)),
- (date(2000, 2, 29),
- Duration(years=-1, months=-1),
- date(1999, 1, 29)),
- (date(2000, 2, 1),
- Duration(years=1, months=1, days=1),
- date(2001, 3, 2)),
- (date(2000, 2, 29),
- Duration(years=1, months=1, days=1),
- date(2001, 3, 30)),
- (date(2000, 2, 29),
- Duration(years=1, days=1),
- date(2001, 3, 1)),
- (date(1996, 2, 29),
- Duration(years=4, days=1),
- date(2000, 3, 1)),
- (date(2096, 2, 29),
- Duration(years=4, days=1),
- date(2100, 3, 1)),
- (date(2000, 2, 1),
- Duration(years=-1, months=-1, days=-1),
- date(1998, 12, 31)),
- (date(2000, 2, 29),
- Duration(years=-1, months=-1, days=-1),
- date(1999, 1, 28)),
- (date(2001, 4, 1),
- Duration(years=-1, months=-1, days=-1),
- date(2000, 2, 29)),
- (date(2000, 4, 1),
- Duration(years=-1, months=-1, days=-1),
- date(1999, 2, 28)),
- (Duration(years=1, months=2),
- Duration(years=0, months=0, days=1),
- Duration(years=1, months=2, days=1)),
- (Duration(years=-1, months=-1, days=-1),
- date(2000, 4, 1),
- date(1999, 2, 28)),
- (Duration(years=1, months=1, weeks=5),
- date(2000, 1, 30),
- date(2001, 4, 4)),
- (parse_duration("P1Y1M5W"),
- date(2000, 1, 30),
- date(2001, 4, 4)),
- (parse_duration("P0.5Y"),
- date(2000, 1, 30),
- None),
- (Duration(years=1, months=1, hours=3),
- datetime(2000, 1, 30, 12, 15, 00),
- datetime(2001, 2, 28, 15, 15, 00)),
- (parse_duration("P1Y1MT3H"),
- datetime(2000, 1, 30, 12, 15, 00),
- datetime(2001, 2, 28, 15, 15, 00)),
- (Duration(years=1, months=2),
- timedelta(days=1),
- Duration(years=1, months=2, days=1)),
- (timedelta(days=1),
- Duration(years=1, months=2),
- Duration(years=1, months=2, days=1)),
- (datetime(2008, 1, 1, 0, 2),
- Duration(months=1),
- datetime(2008, 2, 1, 0, 2)),
- (datetime.strptime("200802", "%Y%M"),
- parse_duration("P1M"),
- datetime(2008, 2, 1, 0, 2)),
- (datetime(2008, 2, 1),
- Duration(months=1),
- datetime(2008, 3, 1)),
- (datetime.strptime("200802", "%Y%m"),
- parse_duration("P1M"),
- datetime(2008, 3, 1)),
+ (date(2000, 2, 1), Duration(years=1, months=1), date(2001, 3, 1)),
+ (date(2000, 2, 29), Duration(years=1, months=1), date(2001, 3, 29)),
+ (date(2000, 2, 29), Duration(years=1), date(2001, 2, 28)),
+ (date(1996, 2, 29), Duration(years=4), date(2000, 2, 29)),
+ (date(2096, 2, 29), Duration(years=4), date(2100, 2, 28)),
+ (date(2000, 2, 1), Duration(years=-1, months=-1), date(1999, 1, 1)),
+ (date(2000, 2, 29), Duration(years=-1, months=-1), date(1999, 1, 29)),
+ (date(2000, 2, 1), Duration(years=1, months=1, days=1), date(2001, 3, 2)),
+ (date(2000, 2, 29), Duration(years=1, months=1, days=1), date(2001, 3, 30)),
+ (date(2000, 2, 29), Duration(years=1, days=1), date(2001, 3, 1)),
+ (date(1996, 2, 29), Duration(years=4, days=1), date(2000, 3, 1)),
+ (date(2096, 2, 29), Duration(years=4, days=1), date(2100, 3, 1)),
+ (date(2000, 2, 1), Duration(years=-1, months=-1, days=-1), date(1998, 12, 31)),
+ (date(2000, 2, 29), Duration(years=-1, months=-1, days=-1), date(1999, 1, 28)),
+ (date(2001, 4, 1), Duration(years=-1, months=-1, days=-1), date(2000, 2, 29)),
+ (date(2000, 4, 1), Duration(years=-1, months=-1, days=-1), date(1999, 2, 28)),
+ (
+ Duration(years=1, months=2),
+ Duration(years=0, months=0, days=1),
+ Duration(years=1, months=2, days=1),
+ ),
+ (Duration(years=-1, months=-1, days=-1), date(2000, 4, 1), date(1999, 2, 28)),
+ (Duration(years=1, months=1, weeks=5), date(2000, 1, 30), date(2001, 4, 4)),
+ (parse_duration("P1Y1M5W"), date(2000, 1, 30), date(2001, 4, 4)),
+ (parse_duration("P0.5Y"), date(2000, 1, 30), None),
+ (
+ Duration(years=1, months=1, hours=3),
+ datetime(2000, 1, 30, 12, 15, 00),
+ datetime(2001, 2, 28, 15, 15, 00),
+ ),
+ (
+ parse_duration("P1Y1MT3H"),
+ datetime(2000, 1, 30, 12, 15, 00),
+ datetime(2001, 2, 28, 15, 15, 00),
+ ),
+ (
+ Duration(years=1, months=2),
+ timedelta(days=1),
+ Duration(years=1, months=2, days=1),
+ ),
+ (
+ timedelta(days=1),
+ Duration(years=1, months=2),
+ Duration(years=1, months=2, days=1),
+ ),
+ (datetime(2008, 1, 1, 0, 2), Duration(months=1), datetime(2008, 2, 1, 0, 2)),
+ (
+ datetime.strptime("200802", "%Y%M"),
+ parse_duration("P1M"),
+ datetime(2008, 2, 1, 0, 2),
+ ),
+ (datetime(2008, 2, 1), Duration(months=1), datetime(2008, 3, 1)),
+ (datetime.strptime("200802", "%Y%m"), parse_duration("P1M"), datetime(2008, 3, 1)),
# (date(2000, 1, 1),
# Duration(years=1.5),
# date(2001, 6, 1)),
# (date(2000, 1, 1),
# Duration(years=1, months=1.5),
# date(2001, 2, 14)),
- )
+)
+
+# A list of test cases of multiplications of durations
+# are compared against a given expected result.
+DATE_MUL_TEST_CASES = (
+ (Duration(years=1, months=1), 3, Duration(years=3, months=3)),
+ (Duration(years=1, months=1), -3, Duration(years=-3, months=-3)),
+ (3, Duration(years=1, months=1), Duration(years=3, months=3)),
+ (-3, Duration(years=1, months=1), Duration(years=-3, months=-3)),
+ (5, Duration(years=2, minutes=40), Duration(years=10, hours=3, minutes=20)),
+ (-5, Duration(years=2, minutes=40), Duration(years=-10, hours=-3, minutes=-20)),
+ (7, Duration(years=1, months=2, weeks=40), Duration(years=8, months=2, weeks=280)),
+)
class DurationTest(unittest.TestCase):
- '''
+ """
This class tests various other aspects of the isoduration module,
which are not covered with the test cases listed above.
- '''
+ """
def test_associative(self):
- '''
+ """
Adding 2 durations to a date is not associative.
- '''
+ """
days1 = Duration(days=1)
months1 = Duration(months=1)
start = date(2000, 3, 30)
@@ -250,42 +228,86 @@ class DurationTest(unittest.TestCase):
self.assertNotEqual(res1, res2)
def test_typeerror(self):
- '''
+ """
Test if TypError is raised with certain parameters.
- '''
+ """
self.assertRaises(TypeError, parse_duration, date(2000, 1, 1))
- self.assertRaises(TypeError, operator.sub, Duration(years=1),
- date(2000, 1, 1))
- self.assertRaises(TypeError, operator.sub, 'raise exc',
- Duration(years=1))
- self.assertRaises(TypeError, operator.add,
- Duration(years=1, months=1, weeks=5),
- 'raise exception')
- self.assertRaises(TypeError, operator.add, 'raise exception',
- Duration(years=1, months=1, weeks=5))
+ self.assertRaises(TypeError, operator.sub, Duration(years=1), date(2000, 1, 1))
+ self.assertRaises(TypeError, operator.sub, "raise exc", Duration(years=1))
+ self.assertRaises(
+ TypeError,
+ operator.add,
+ Duration(years=1, months=1, weeks=5),
+ "raise exception",
+ )
+ self.assertRaises(
+ TypeError,
+ operator.add,
+ "raise exception",
+ Duration(years=1, months=1, weeks=5),
+ )
+ self.assertRaises(
+ TypeError,
+ operator.mul,
+ Duration(years=1, months=1, weeks=5),
+ "raise exception",
+ )
+ self.assertRaises(
+ TypeError,
+ operator.mul,
+ "raise exception",
+ Duration(years=1, months=1, weeks=5),
+ )
+ self.assertRaises(
+ TypeError, operator.mul, Duration(years=1, months=1, weeks=5), 3.14
+ )
+ self.assertRaises(
+ TypeError, operator.mul, 3.14, Duration(years=1, months=1, weeks=5)
+ )
def test_parseerror(self):
- '''
+ """
Test for unparseable duration string.
- '''
- self.assertRaises(ISO8601Error, parse_duration, 'T10:10:10')
+ """
+ self.assertRaises(ISO8601Error, parse_duration, "T10:10:10")
def test_repr(self):
- '''
- Test __repr__ and __str__ for Duration obqects.
- '''
+ """
+ Test __repr__ and __str__ for Duration objects.
+ """
dur = Duration(10, 10, years=10, months=10)
- self.assertEqual('10 years, 10 months, 10 days, 0:00:10', str(dur))
- self.assertEqual('isodate.duration.Duration(10, 10, 0,'
- ' years=10, months=10)', repr(dur))
+ self.assertEqual("10 years, 10 months, 10 days, 0:00:10", str(dur))
+ self.assertEqual(
+ "isodate.duration.Duration(10, 10, 0," " years=10, months=10)", repr(dur)
+ )
+ dur = Duration(months=0)
+ self.assertEqual("0:00:00", str(dur))
+ dur = Duration(months=1)
+ self.assertEqual("1 month, 0:00:00", str(dur))
+
+ def test_hash(self):
+ """
+ Test __hash__ for Duration objects.
+ """
+ dur1 = Duration(10, 10, years=10, months=10)
+ dur2 = Duration(9, 9, years=9, months=9)
+ dur3 = Duration(10, 10, years=10, months=10)
+ self.assertNotEqual(hash(dur1), hash(dur2))
+ self.assertNotEqual(id(dur1), id(dur2))
+ self.assertEqual(hash(dur1), hash(dur3))
+ self.assertNotEqual(id(dur1), id(dur3))
+ durSet = set()
+ durSet.add(dur1)
+ durSet.add(dur2)
+ durSet.add(dur3)
+ self.assertEqual(len(durSet), 2)
def test_neg(self):
- '''
+ """
Test __neg__ for Duration objects.
- '''
+ """
self.assertEqual(-Duration(0), Duration(0))
- self.assertEqual(-Duration(years=1, months=1),
- Duration(years=-1, months=-1))
+ self.assertEqual(-Duration(years=1, months=1), Duration(years=-1, months=-1))
self.assertEqual(-Duration(years=1, months=1), Duration(months=-13))
self.assertNotEqual(-Duration(years=1), timedelta(days=-365))
self.assertNotEqual(-timedelta(days=365), Duration(years=-1))
@@ -294,41 +316,34 @@ class DurationTest(unittest.TestCase):
# self.assertNotEqual(-timedelta(days=10), -Duration(days=10))
def test_format(self):
- '''
+ """
Test various other strftime combinations.
- '''
- self.assertEqual(duration_isoformat(Duration(0)), 'P0D')
- self.assertEqual(duration_isoformat(-Duration(0)), 'P0D')
- self.assertEqual(duration_isoformat(Duration(seconds=10)), 'PT10S')
- self.assertEqual(duration_isoformat(Duration(years=-1, months=-1)),
- '-P1Y1M')
- self.assertEqual(duration_isoformat(-Duration(years=1, months=1)),
- '-P1Y1M')
- self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)),
- 'P1Y1M')
- self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)),
- 'P1Y1M')
- dur = Duration(years=3, months=7, days=23, hours=5, minutes=25,
- milliseconds=330)
- self.assertEqual(duration_isoformat(dur), 'P3Y7M23DT5H25M0.33S')
- self.assertEqual(duration_isoformat(-dur), '-P3Y7M23DT5H25M0.33S')
+ """
+ self.assertEqual(duration_isoformat(Duration(0)), "P0D")
+ self.assertEqual(duration_isoformat(-Duration(0)), "P0D")
+ self.assertEqual(duration_isoformat(Duration(seconds=10)), "PT10S")
+ self.assertEqual(duration_isoformat(Duration(years=-1, months=-1)), "-P1Y1M")
+ self.assertEqual(duration_isoformat(-Duration(years=1, months=1)), "-P1Y1M")
+ self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)), "P1Y1M")
+ self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)), "P1Y1M")
+ dur = Duration(
+ years=3, months=7, days=23, hours=5, minutes=25, milliseconds=330
+ )
+ self.assertEqual(duration_isoformat(dur), "P3Y7M23DT5H25M0.33S")
+ self.assertEqual(duration_isoformat(-dur), "-P3Y7M23DT5H25M0.33S")
def test_equal(self):
- '''
+ """
Test __eq__ and __ne__ methods.
- '''
- self.assertEqual(Duration(years=1, months=1),
- Duration(years=1, months=1))
+ """
+ self.assertEqual(Duration(years=1, months=1), Duration(years=1, months=1))
self.assertEqual(Duration(years=1, months=1), Duration(months=13))
- self.assertNotEqual(Duration(years=1, months=2),
- Duration(years=1, months=1))
+ self.assertNotEqual(Duration(years=1, months=2), Duration(years=1, months=1))
self.assertNotEqual(Duration(years=1, months=1), Duration(months=14))
self.assertNotEqual(Duration(years=1), timedelta(days=365))
- self.assertFalse(Duration(years=1, months=1) !=
- Duration(years=1, months=1))
+ self.assertFalse(Duration(years=1, months=1) != Duration(years=1, months=1))
self.assertFalse(Duration(years=1, months=1) != Duration(months=13))
- self.assertTrue(Duration(years=1, months=2) !=
- Duration(years=1, months=1))
+ self.assertTrue(Duration(years=1, months=2) != Duration(years=1, months=1))
self.assertTrue(Duration(years=1, months=1) != Duration(months=14))
self.assertTrue(Duration(years=1) != timedelta(days=365))
self.assertEqual(Duration(days=1), timedelta(days=1))
@@ -337,15 +352,13 @@ class DurationTest(unittest.TestCase):
# self.assertNotEqual(timedelta(days=1), Duration(days=1))
def test_totimedelta(self):
- '''
+ """
Test conversion form Duration to timedelta.
- '''
+ """
dur = Duration(years=1, months=2, days=10)
- self.assertEqual(dur.totimedelta(datetime(1998, 2, 25)),
- timedelta(434))
+ self.assertEqual(dur.totimedelta(datetime(1998, 2, 25)), timedelta(434))
# leap year has one day more in february
- self.assertEqual(dur.totimedelta(datetime(2000, 2, 25)),
- timedelta(435))
+ self.assertEqual(dur.totimedelta(datetime(2000, 2, 25)), timedelta(435))
dur = Duration(months=2)
# march is longer than february, but april is shorter than
# march (cause only one day difference compared to 2)
@@ -363,31 +376,31 @@ def create_parsetestcase(durationstring, expectation, format, altstr):
"""
class TestParseDuration(unittest.TestCase):
- '''
+ """
A test case template to parse an ISO duration string into a
timedelta or Duration object.
- '''
+ """
def test_parse(self):
- '''
+ """
Parse an ISO duration string and compare it to the expected value.
- '''
+ """
result = parse_duration(durationstring)
self.assertEqual(result, expectation)
def test_format(self):
- '''
+ """
Take duration/timedelta object and create ISO string from it.
This is the reverse test to test_parse.
- '''
+ """
if altstr:
- self.assertEqual(duration_isoformat(expectation, format),
- altstr)
+ self.assertEqual(duration_isoformat(expectation, format), altstr)
else:
# if durationstring == '-P2W':
# import pdb; pdb.set_trace()
- self.assertEqual(duration_isoformat(expectation, format),
- durationstring)
+ self.assertEqual(
+ duration_isoformat(expectation, format), durationstring
+ )
return unittest.TestLoader().loadTestsFromTestCase(TestParseDuration)
@@ -406,34 +419,36 @@ def create_mathtestcase(dur1, dur2, resadd, ressub, resge):
ressub = parse_duration(ressub)
class TestMathDuration(unittest.TestCase):
- '''
+ """
A test case template test addition, subtraction and >
operators for Duration objects.
- '''
+ """
def test_add(self):
- '''
+ """
Test operator + (__add__, __radd__)
- '''
+ """
self.assertEqual(dur1 + dur2, resadd)
def test_sub(self):
- '''
+ """
Test operator - (__sub__, __rsub__)
- '''
+ """
self.assertEqual(dur1 - dur2, ressub)
def test_ge(self):
- '''
+ """
Test operator > and <
- '''
+ """
+
def dogetest():
- ''' Test greater than.'''
+ """Test greater than."""
return dur1 > dur2
def doletest():
- ''' Test less than.'''
+ """Test less than."""
return dur1 < dur2
+
if resge is None:
self.assertRaises(TypeError, dogetest)
self.assertRaises(TypeError, doletest)
@@ -453,21 +468,21 @@ def create_datetestcase(start, tdelta, duration):
"""
class TestDateCalc(unittest.TestCase):
- '''
+ """
A test case template test addition, subtraction
operators for Duration objects.
- '''
+ """
def test_add(self):
- '''
+ """
Test operator +.
- '''
+ """
self.assertEqual(start + tdelta, start + duration)
def test_sub(self):
- '''
+ """
Test operator -.
- '''
+ """
self.assertEqual(start - tdelta, start - duration)
return unittest.TestLoader().loadTestsFromTestCase(TestDateCalc)
@@ -482,14 +497,14 @@ def create_datecalctestcase(start, duration, expectation):
"""
class TestDateCalc(unittest.TestCase):
- '''
+ """
A test case template test addition operators for Duration objects.
- '''
+ """
def test_calc(self):
- '''
+ """
Test operator +.
- '''
+ """
if expectation is None:
self.assertRaises(ValueError, operator.add, start, duration)
else:
@@ -498,21 +513,43 @@ def create_datecalctestcase(start, duration, expectation):
return unittest.TestLoader().loadTestsFromTestCase(TestDateCalc)
+def create_datemultestcase(operand1, operand2, expectation):
+ """
+ Create a TestCase class for a specific test.
+
+ This allows having a separate TestCase for each test tuple from the
+ DATE_CALC_TEST_CASES list, so that a failed test won't stop other tests.
+ """
+
+ class TestDateMul(unittest.TestCase):
+ """
+ A test case template test addition operators for Duration objects.
+ """
+
+ def test_mul(self):
+ """
+ Test operator *.
+ """
+ self.assertEqual(operand1 * operand2, expectation)
+
+ return unittest.TestLoader().loadTestsFromTestCase(TestDateMul)
+
+
def test_suite():
- '''
+ """
Return a test suite containing all test defined above.
- '''
+ """
suite = unittest.TestSuite()
- for durationstring, (expectation, format,
- altstr) in PARSE_TEST_CASES.items():
- suite.addTest(create_parsetestcase(durationstring, expectation,
- format, altstr))
+ for durationstring, (expectation, format, altstr) in PARSE_TEST_CASES.items():
+ suite.addTest(create_parsetestcase(durationstring, expectation, format, altstr))
for testdata in MATH_TEST_CASES:
suite.addTest(create_mathtestcase(*testdata))
for testdata in DATE_TEST_CASES:
suite.addTest(create_datetestcase(*testdata))
for testdata in DATE_CALC_TEST_CASES:
suite.addTest(create_datecalctestcase(*testdata))
+ for testdata in DATE_MUL_TEST_CASES:
+ suite.addTest(create_datemultestcase(*testdata))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(DurationTest))
return suite
@@ -521,5 +558,6 @@ def test_suite():
def load_tests(loader, tests, pattern):
return test_suite()
-if __name__ == '__main__':
- unittest.main(defaultTest='test_suite')
+
+if __name__ == "__main__":
+ unittest.main(defaultTest="test_suite")
diff --git a/src/isodate/tests/test_pickle.py b/src/isodate/tests/test_pickle.py
index b52f8cb..01b56e6 100644
--- a/src/isodate/tests/test_pickle.py
+++ b/src/isodate/tests/test_pickle.py
@@ -1,29 +1,31 @@
import unittest
-import cPickle as pickle
+
+import pickle
+
import isodate
class TestPickle(unittest.TestCase):
- '''
+ """
A test case template to parse an ISO datetime string into a
datetime object.
- '''
+ """
def test_pickle_datetime(self):
- '''
+ """
Parse an ISO datetime string and compare it to the expected value.
- '''
- dti = isodate.parse_datetime('2012-10-26T09:33+00:00')
+ """
+ dti = isodate.parse_datetime("2012-10-26T09:33+00:00")
for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
pikl = pickle.dumps(dti, proto)
- self.assertEqual(dti, pickle.loads(pikl),
- "pickle proto %d failed" % proto)
+ self.assertEqual(dti, pickle.loads(pikl), "pickle proto %d failed" % proto)
def test_pickle_duration(self):
- '''
+ """
Pickle / unpickle duration objects.
- '''
+ """
from isodate.duration import Duration
+
dur = Duration()
failed = []
for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
@@ -31,16 +33,21 @@ class TestPickle(unittest.TestCase):
pikl = pickle.dumps(dur, proto)
if dur != pickle.loads(pikl):
raise Exception("not equal")
- except Exception, e:
+ except Exception as e:
failed.append("pickle proto %d failed (%s)" % (proto, repr(e)))
- self.assertEqual(len(failed), 0, "pickle protos failed: %s" %
- str(failed))
+ self.assertEqual(len(failed), 0, "pickle protos failed: %s" % str(failed))
+
+ def test_pickle_utc(self):
+ """
+ isodate.UTC objects remain the same after pickling.
+ """
+ self.assertTrue(isodate.UTC is pickle.loads(pickle.dumps(isodate.UTC)))
def test_suite():
- '''
+ """
Construct a TestSuite instance for all test cases.
- '''
+ """
suite = unittest.TestSuite()
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestPickle))
return suite
@@ -50,5 +57,6 @@ def test_suite():
def load_tests(loader, tests, pattern):
return test_suite()
-if __name__ == '__main__':
- unittest.main(defaultTest='test_suite')
+
+if __name__ == "__main__":
+ unittest.main(defaultTest="test_suite")
diff --git a/src/isodate/tests/test_strf.py b/src/isodate/tests/test_strf.py
index 37a135b..701c877 100644
--- a/src/isodate/tests/test_strf.py
+++ b/src/isodate/tests/test_strf.py
@@ -1,32 +1,6 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
Test cases for the isodate module.
-'''
+"""
import unittest
import time
from datetime import datetime, timedelta
@@ -36,19 +10,30 @@ from isodate import DT_EXT_COMPLETE
from isodate import tzinfo
-TEST_CASES = ((datetime(2012, 12, 25, 13, 30, 0, 0, LOCAL), DT_EXT_COMPLETE,
- "2012-12-25T13:30:00+10:00"),
- # DST ON
- (datetime(1999, 12, 25, 13, 30, 0, 0, LOCAL), DT_EXT_COMPLETE,
- "1999-12-25T13:30:00+11:00"),
- # microseconds
- (datetime(2012, 10, 12, 8, 29, 46, 69178),
- "%Y-%m-%dT%H:%M:%S.%f",
- "2012-10-12T08:29:46.069178"),
- (datetime(2012, 10, 12, 8, 29, 46, 691780),
- "%Y-%m-%dT%H:%M:%S.%f",
- "2012-10-12T08:29:46.691780"),
- )
+TEST_CASES = (
+ (
+ datetime(2012, 12, 25, 13, 30, 0, 0, LOCAL),
+ DT_EXT_COMPLETE,
+ "2012-12-25T13:30:00+10:00",
+ ),
+ # DST ON
+ (
+ datetime(1999, 12, 25, 13, 30, 0, 0, LOCAL),
+ DT_EXT_COMPLETE,
+ "1999-12-25T13:30:00+11:00",
+ ),
+ # microseconds
+ (
+ datetime(2012, 10, 12, 8, 29, 46, 69178),
+ "%Y-%m-%dT%H:%M:%S.%f",
+ "2012-10-12T08:29:46.069178",
+ ),
+ (
+ datetime(2012, 10, 12, 8, 29, 46, 691780),
+ "%Y-%m-%dT%H:%M:%S.%f",
+ "2012-10-12T08:29:46.691780",
+ ),
+)
def create_testcase(dt, format, expectation):
@@ -60,9 +45,9 @@ def create_testcase(dt, format, expectation):
"""
class TestDate(unittest.TestCase):
- '''
+ """
A test case template to test ISO date formatting.
- '''
+ """
# local time zone mock function
def localtime_mock(self, secs):
@@ -70,23 +55,31 @@ def create_testcase(dt, format, expectation):
mock time.localtime so that it always returns a time_struct with
tm_idst=1
"""
- tt = self.ORIG['localtime'](secs)
+ tt = self.ORIG["localtime"](secs)
# befor 2000 everything is dst, after 2000 no dst.
if tt.tm_year < 2000:
dst = 1
else:
dst = 0
- tt = (tt.tm_year, tt.tm_mon, tt.tm_mday,
- tt.tm_hour, tt.tm_min, tt.tm_sec,
- tt.tm_wday, tt.tm_yday, dst)
+ tt = (
+ tt.tm_year,
+ tt.tm_mon,
+ tt.tm_mday,
+ tt.tm_hour,
+ tt.tm_min,
+ tt.tm_sec,
+ tt.tm_wday,
+ tt.tm_yday,
+ dst,
+ )
return time.struct_time(tt)
def setUp(self):
self.ORIG = {}
- self.ORIG['STDOFFSET'] = tzinfo.STDOFFSET
- self.ORIG['DSTOFFSET'] = tzinfo.DSTOFFSET
- self.ORIG['DSTDIFF'] = tzinfo.DSTDIFF
- self.ORIG['localtime'] = time.localtime
+ self.ORIG["STDOFFSET"] = tzinfo.STDOFFSET
+ self.ORIG["DSTOFFSET"] = tzinfo.DSTOFFSET
+ self.ORIG["DSTDIFF"] = tzinfo.DSTDIFF
+ self.ORIG["localtime"] = time.localtime
# ovveride all saved values with fixtures.
# calculate LOCAL TZ offset, so that this test runs in
# every time zone
@@ -97,30 +90,28 @@ def create_testcase(dt, format, expectation):
def tearDown(self):
# restore test fixtures
- tzinfo.STDOFFSET = self.ORIG['STDOFFSET']
- tzinfo.DSTOFFSET = self.ORIG['DSTOFFSET']
- tzinfo.DSTDIFF = self.ORIG['DSTDIFF']
- time.localtime = self.ORIG['localtime']
+ tzinfo.STDOFFSET = self.ORIG["STDOFFSET"]
+ tzinfo.DSTOFFSET = self.ORIG["DSTOFFSET"]
+ tzinfo.DSTDIFF = self.ORIG["DSTDIFF"]
+ time.localtime = self.ORIG["localtime"]
def test_format(self):
- '''
+ """
Take date object and create ISO string from it.
This is the reverse test to test_parse.
- '''
+ """
if expectation is None:
- self.assertRaises(AttributeError,
- strftime(dt, format))
+ self.assertRaises(AttributeError, strftime(dt, format))
else:
- self.assertEqual(strftime(dt, format),
- expectation)
+ self.assertEqual(strftime(dt, format), expectation)
return unittest.TestLoader().loadTestsFromTestCase(TestDate)
def test_suite():
- '''
+ """
Construct a TestSuite instance for all test cases.
- '''
+ """
suite = unittest.TestSuite()
for dt, format, expectation in TEST_CASES:
suite.addTest(create_testcase(dt, format, expectation))
@@ -131,5 +122,6 @@ def test_suite():
def load_tests(loader, tests, pattern):
return test_suite()
-if __name__ == '__main__':
- unittest.main(defaultTest='test_suite')
+
+if __name__ == "__main__":
+ unittest.main(defaultTest="test_suite")
diff --git a/src/isodate/tests/test_time.py b/src/isodate/tests/test_time.py
index cc5ec08..d369714 100644
--- a/src/isodate/tests/test_time.py
+++ b/src/isodate/tests/test_time.py
@@ -1,32 +1,6 @@
-##############################################################################
-# Copyright 2009, Gerhard Weis
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the authors nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT
-##############################################################################
-'''
+"""
Test cases for the isotime module.
-'''
+"""
import unittest
from datetime import time
@@ -39,51 +13,78 @@ from isodate import TZ_BAS, TZ_EXT, TZ_HOUR
# the following list contains tuples of ISO time strings and the expected
# result from the parse_time method. A result of None means an ISO8601Error
# is expected.
-TEST_CASES = [('232050', time(23, 20, 50), TIME_BAS_COMPLETE + TZ_BAS),
- ('23:20:50', time(23, 20, 50), TIME_EXT_COMPLETE + TZ_EXT),
- ('2320', time(23, 20), TIME_BAS_MINUTE),
- ('23:20', time(23, 20), TIME_EXT_MINUTE),
- ('23', time(23), TIME_HOUR),
- ('232050,5', time(23, 20, 50, 500000), None),
- ('23:20:50.5', time(23, 20, 50, 500000), None),
- # test precision
- ('15:33:42.123456', time(15, 33, 42, 123456), None),
- ('15:33:42.1234564', time(15, 33, 42, 123456), None),
- ('15:33:42.1234557', time(15, 33, 42, 123456), None),
- ('2320,8', time(23, 20, 48), None),
- ('23:20,8', time(23, 20, 48), None),
- ('23,3', time(23, 18), None),
- ('232030Z', time(23, 20, 30, tzinfo=UTC),
- TIME_BAS_COMPLETE + TZ_BAS),
- ('2320Z', time(23, 20, tzinfo=UTC), TIME_BAS_MINUTE + TZ_BAS),
- ('23Z', time(23, tzinfo=UTC), TIME_HOUR + TZ_BAS),
- ('23:20:30Z', time(23, 20, 30, tzinfo=UTC),
- TIME_EXT_COMPLETE + TZ_EXT),
- ('23:20Z', time(23, 20, tzinfo=UTC), TIME_EXT_MINUTE + TZ_EXT),
- ('152746+0100', time(15, 27, 46,
- tzinfo=FixedOffset(1, 0, '+0100')), TIME_BAS_COMPLETE + TZ_BAS),
- ('152746-0500', time(15, 27, 46,
- tzinfo=FixedOffset(-5, 0, '-0500')),
- TIME_BAS_COMPLETE + TZ_BAS),
- ('152746+01', time(15, 27, 46,
- tzinfo=FixedOffset(1, 0, '+01:00')),
- TIME_BAS_COMPLETE + TZ_HOUR),
- ('152746-05', time(15, 27, 46,
- tzinfo=FixedOffset(-5, -0, '-05:00')),
- TIME_BAS_COMPLETE + TZ_HOUR),
- ('15:27:46+01:00', time(15, 27, 46,
- tzinfo=FixedOffset(1, 0, '+01:00')),
- TIME_EXT_COMPLETE + TZ_EXT),
- ('15:27:46-05:00', time(15, 27, 46,
- tzinfo=FixedOffset(-5, -0, '-05:00')),
- TIME_EXT_COMPLETE + TZ_EXT),
- ('15:27:46+01', time(15, 27, 46,
- tzinfo=FixedOffset(1, 0, '+01:00')),
- TIME_EXT_COMPLETE + TZ_HOUR),
- ('15:27:46-05', time(15, 27, 46,
- tzinfo=FixedOffset(-5, -0, '-05:00')),
- TIME_EXT_COMPLETE + TZ_HOUR),
- ('1:17:30', None, TIME_EXT_COMPLETE)]
+TEST_CASES = [
+ ("232050", time(23, 20, 50), TIME_BAS_COMPLETE + TZ_BAS),
+ ("23:20:50", time(23, 20, 50), TIME_EXT_COMPLETE + TZ_EXT),
+ ("2320", time(23, 20), TIME_BAS_MINUTE),
+ ("23:20", time(23, 20), TIME_EXT_MINUTE),
+ ("23", time(23), TIME_HOUR),
+ ("232050,5", time(23, 20, 50, 500000), None),
+ ("23:20:50.5", time(23, 20, 50, 500000), None),
+ # test precision
+ ("15:33:42.123456", time(15, 33, 42, 123456), None),
+ ("15:33:42.1234564", time(15, 33, 42, 123456), None),
+ ("15:33:42.1234557", time(15, 33, 42, 123456), None),
+ ("2320,8", time(23, 20, 48), None),
+ ("23:20,8", time(23, 20, 48), None),
+ ("23,3", time(23, 18), None),
+ ("232030Z", time(23, 20, 30, tzinfo=UTC), TIME_BAS_COMPLETE + TZ_BAS),
+ ("2320Z", time(23, 20, tzinfo=UTC), TIME_BAS_MINUTE + TZ_BAS),
+ ("23Z", time(23, tzinfo=UTC), TIME_HOUR + TZ_BAS),
+ ("23:20:30Z", time(23, 20, 30, tzinfo=UTC), TIME_EXT_COMPLETE + TZ_EXT),
+ ("23:20Z", time(23, 20, tzinfo=UTC), TIME_EXT_MINUTE + TZ_EXT),
+ (
+ "152746+0100",
+ time(15, 27, 46, tzinfo=FixedOffset(1, 0, "+0100")),
+ TIME_BAS_COMPLETE + TZ_BAS,
+ ),
+ (
+ "152746-0500",
+ time(15, 27, 46, tzinfo=FixedOffset(-5, 0, "-0500")),
+ TIME_BAS_COMPLETE + TZ_BAS,
+ ),
+ (
+ "152746+01",
+ time(15, 27, 46, tzinfo=FixedOffset(1, 0, "+01:00")),
+ TIME_BAS_COMPLETE + TZ_HOUR,
+ ),
+ (
+ "152746-05",
+ time(15, 27, 46, tzinfo=FixedOffset(-5, -0, "-05:00")),
+ TIME_BAS_COMPLETE + TZ_HOUR,
+ ),
+ (
+ "15:27:46+01:00",
+ time(15, 27, 46, tzinfo=FixedOffset(1, 0, "+01:00")),
+ TIME_EXT_COMPLETE + TZ_EXT,
+ ),
+ (
+ "15:27:46-05:00",
+ time(15, 27, 46, tzinfo=FixedOffset(-5, -0, "-05:00")),
+ TIME_EXT_COMPLETE + TZ_EXT,
+ ),
+ (
+ "15:27:46+01",
+ time(15, 27, 46, tzinfo=FixedOffset(1, 0, "+01:00")),
+ TIME_EXT_COMPLETE + TZ_HOUR,
+ ),
+ (
+ "15:27:46-05",
+ time(15, 27, 46, tzinfo=FixedOffset(-5, -0, "-05:00")),
+ TIME_EXT_COMPLETE + TZ_HOUR,
+ ),
+ (
+ "15:27:46-05:30",
+ time(15, 27, 46, tzinfo=FixedOffset(-5, -30, "-05:30")),
+ TIME_EXT_COMPLETE + TZ_EXT,
+ ),
+ (
+ "15:27:46-0545",
+ time(15, 27, 46, tzinfo=FixedOffset(-5, -45, "-0545")),
+ TIME_EXT_COMPLETE + TZ_BAS,
+ ),
+ ("1:17:30", None, TIME_EXT_COMPLETE),
+]
def create_testcase(timestring, expectation, format):
@@ -95,15 +96,15 @@ def create_testcase(timestring, expectation, format):
"""
class TestTime(unittest.TestCase):
- '''
+ """
A test case template to parse an ISO time string into a time
object.
- '''
+ """
def test_parse(self):
- '''
+ """
Parse an ISO time string and compare it to the expected value.
- '''
+ """
if expectation is None:
self.assertRaises(ISO8601Error, parse_time, timestring)
else:
@@ -111,24 +112,22 @@ def create_testcase(timestring, expectation, format):
self.assertEqual(result, expectation)
def test_format(self):
- '''
+ """
Take time object and create ISO string from it.
This is the reverse test to test_parse.
- '''
+ """
if expectation is None:
- self.assertRaises(AttributeError,
- time_isoformat, expectation, format)
+ self.assertRaises(AttributeError, time_isoformat, expectation, format)
elif format is not None:
- self.assertEqual(time_isoformat(expectation, format),
- timestring)
+ self.assertEqual(time_isoformat(expectation, format), timestring)
return unittest.TestLoader().loadTestsFromTestCase(TestTime)
def test_suite():
- '''
+ """
Construct a TestSuite instance for all test cases.
- '''
+ """
suite = unittest.TestSuite()
for timestring, expectation, format in TEST_CASES:
suite.addTest(create_testcase(timestring, expectation, format))
@@ -139,5 +138,6 @@ def test_suite():
def load_tests(loader, tests, pattern):
return test_suite()
-if __name__ == '__main__':
- unittest.main(defaultTest='test_suite')
+
+if __name__ == "__main__":
+ unittest.main(defaultTest="test_suite")
diff --git a/src/isodate/tzinfo.py b/src/isodate/tzinfo.py
index b41f058..102b6e3 100644
--- a/src/isodate/tzinfo.py
+++ b/src/isodate/tzinfo.py
@@ -1,8 +1,8 @@
-'''
+"""
This module provides some datetime.tzinfo implementations.
All those classes are taken from the Python documentation.
-'''
+"""
from datetime import timedelta, tzinfo
import time
@@ -11,76 +11,90 @@ ZERO = timedelta(0)
class Utc(tzinfo):
- '''UTC
+ """UTC
Universal time coordinated time zone.
- '''
+ """
def utcoffset(self, dt):
- '''
+ """
Return offset from UTC in minutes east of UTC, which is ZERO for UTC.
- '''
+ """
return ZERO
def tzname(self, dt):
- '''
+ """
Return the time zone name corresponding to the datetime object dt,
as a string.
- '''
+ """
return "UTC"
def dst(self, dt):
- '''
+ """
Return the daylight saving time (DST) adjustment, in minutes east
of UTC.
- '''
+ """
return ZERO
+ def __reduce__(self):
+ """
+ When unpickling a Utc object, return the default instance below, UTC.
+ """
+ return _Utc, ()
+
+
UTC = Utc()
# the default instance for UTC.
+def _Utc():
+ """
+ Helper function for unpickling a Utc object.
+ """
+ return UTC
+
+
class FixedOffset(tzinfo):
- '''
+ """
A class building tzinfo objects for fixed-offset time zones.
Note that FixedOffset(0, 0, "UTC") or FixedOffset() is a different way to
build a UTC tzinfo object.
- '''
+ """
def __init__(self, offset_hours=0, offset_minutes=0, name="UTC"):
- '''
+ """
Initialise an instance with time offset and name.
The time offset should be positive for time zones east of UTC
and negate for time zones west of UTC.
- '''
+ """
self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes)
self.__name = name
def utcoffset(self, dt):
- '''
+ """
Return offset from UTC in minutes of UTC.
- '''
+ """
return self.__offset
def tzname(self, dt):
- '''
+ """
Return the time zone name corresponding to the datetime object dt, as a
string.
- '''
+ """
return self.__name
def dst(self, dt):
- '''
+ """
Return the daylight saving time (DST) adjustment, in minutes east of
UTC.
- '''
+ """
return ZERO
def __repr__(self):
- '''
+ """
Return nicely formatted repr string.
- '''
+ """
return "<FixedOffset %r>" % self.__name
@@ -103,40 +117,49 @@ class LocalTimezone(tzinfo):
"""
def utcoffset(self, dt):
- '''
+ """
Return offset from UTC in minutes of UTC.
- '''
+ """
if self._isdst(dt):
return DSTOFFSET
else:
return STDOFFSET
def dst(self, dt):
- '''
+ """
Return daylight saving offset.
- '''
+ """
if self._isdst(dt):
return DSTDIFF
else:
return ZERO
def tzname(self, dt):
- '''
+ """
Return the time zone name corresponding to the datetime object dt, as a
string.
- '''
+ """
return time.tzname[self._isdst(dt)]
def _isdst(self, dt):
- '''
+ """
Returns true if DST is active for given datetime object dt.
- '''
- tt = (dt.year, dt.month, dt.day,
- dt.hour, dt.minute, dt.second,
- dt.weekday(), 0, -1)
+ """
+ tt = (
+ dt.year,
+ dt.month,
+ dt.day,
+ dt.hour,
+ dt.minute,
+ dt.second,
+ dt.weekday(),
+ 0,
+ -1,
+ )
stamp = time.mktime(tt)
tt = time.localtime(stamp)
return tt.tm_isdst > 0
-LOCAL = LocalTimezone()
+
# the default instance for local time zone.
+LOCAL = LocalTimezone()
diff --git a/tox.ini b/tox.ini
index 904b4da..322d454 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,20 +1,21 @@
[tox]
-envlist = py26,py27,py32,py33,py34,pypy,flake,cover
+envlist =
+ lint
+ py{37, 38, 39, 310, py3}
[testenv]
deps =
+setenv =
+ PYTHONWARNINGS = default
commands =
{envpython} setup.py test
-
-[testenv:flake]
-basepython = python2.7
-commands=
- pip install --quiet flake8
- {envpython} setup.py clean --all flake8
-
-[testenv:cover]
-basepython = python2.7
-commands =
pip install --quiet coverage
{envpython} setup.py clean --all
coverage run setup.py test
+ coverage xml
+
+[testenv:lint]
+deps = pre-commit
+commands = pre-commit run --all-files
+skip_install = true
+passenv = PRE_COMMIT_COLOR