[Python-modules-commits] [isodate] 01/02: Imported Upstream version 0.4.6

Maximiliano Curia maxy at moszumanska.debian.org
Wed Sep 10 07:51:13 UTC 2014


This is an automated email from the git hooks/post-receive script.

maxy pushed a commit to branch master
in repository isodate.

commit af0542a9ae9f3cd638d3b2e27b02188b91cb5f52
Author: Maximiliano Curia <maxy at gnuservers.com.ar>
Date:   Wed Sep 10 09:19:37 2014 +0200

    Imported Upstream version 0.4.6
---
 .hg_archival.txt                   |   5 +
 .hgignore                          |  12 +
 .hgtags                            |   4 +
 .project                           |  17 ++
 CHANGES.txt                        |  46 ++++
 MANIFEST.in                        |   2 +
 README.txt                         | 103 ++++++++
 TODO.txt                           |  39 +++
 bootstrap.py                       |  62 +++++
 buildout.cfg                       |  32 +++
 setup.py                           |  81 +++++++
 src/isodate/__init__.py            |  55 +++++
 src/isodate/duration.py            | 261 ++++++++++++++++++++
 src/isodate/isodates.py            | 201 ++++++++++++++++
 src/isodate/isodatetime.py         |  61 +++++
 src/isodate/isoduration.py         | 140 +++++++++++
 src/isodate/isoerror.py            |  32 +++
 src/isodate/isostrf.py             | 207 ++++++++++++++++
 src/isodate/isotime.py             | 156 ++++++++++++
 src/isodate/isotzinfo.py           | 108 +++++++++
 src/isodate/tests/__init__.py      |  45 ++++
 src/isodate/tests/test_date.py     | 122 ++++++++++
 src/isodate/tests/test_datetime.py | 110 +++++++++
 src/isodate/tests/test_duration.py | 479 +++++++++++++++++++++++++++++++++++++
 src/isodate/tests/test_time.py     | 139 +++++++++++
 src/isodate/tzinfo.py              | 137 +++++++++++
 26 files changed, 2656 insertions(+)

diff --git a/.hg_archival.txt b/.hg_archival.txt
new file mode 100644
index 0000000..34d0e69
--- /dev/null
+++ b/.hg_archival.txt
@@ -0,0 +1,5 @@
+repo: b8b3628077e2d5fc513caa08b3e7aa1254fd79c4
+node: 8e37a5ab1100234ff09d3bf8960616a2b2a1cd61
+branch: default
+latesttag: RELEASE-0.4.6
+latesttagdistance: 1
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 0000000..c72a241
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,12 @@
+syntax: glob
+*.pyc
+isodate.egg-info
+build/
+.installed.cfg
+.pydevproject
+.settings
+bin
+develop-eggs
+dist
+parts
+MANIFEST
\ No newline at end of file
diff --git a/.hgtags b/.hgtags
new file mode 100644
index 0000000..83cb7ef
--- /dev/null
+++ b/.hgtags
@@ -0,0 +1,4 @@
+e6cd4b2e543e37ed278321374b63c41c3b4e9e68 RELEASE-0.3.0
+010c27908bd8ba7b0c1564abc7840d962f700812 RELEASE-0.4.0
+2b6b173b7feb80eb49e06c828d64e381aca17756 RELEASE-0.4.5
+22cb3855ca1e55931d5c0a3cf7b070f2475963b7 RELEASE-0.4.6
diff --git a/.project b/.project
new file mode 100644
index 0000000..b4db36c
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>isodate</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.python.pydev.PyDevBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.python.pydev.pythonNature</nature>
+	</natures>
+</projectDescription>
diff --git a/CHANGES.txt b/CHANGES.txt
new file mode 100644
index 0000000..cb1a7a0
--- /dev/null
+++ b/CHANGES.txt
@@ -0,0 +1,46 @@
+
+CHANGES
+=======
+
+0.4.6 (2012-01-06)
+------------------
+
+- added Python 3 compatibility via 2to3
+
+0.4.5 (2012-01-06)
+------------------
+
+- made setuptools dependency optional
+
+0.4.4 (2011-04-16)
+------------------
+
+- Fixed formatting of microseconds for datetime objects
+
+0.4.3 (2010-10-29)
+------------------
+
+- Fixed problem with %P formating and fractions (supplied by David Brooks)
+
+0.4.2 (2010-10-28)
+------------------
+
+- Implemented unary - for Duration (supplied by David Brooks)
+- Output fractional seconds with '%P' format. (partly supplied by David Brooks)
+
+0.4.1 (2010-10-13)
+------------------
+
+- fixed bug in comparison between timedelta and Duration.
+- fixed precision problem with microseconds (reported by Tommi Virtanen)
+
+0.4.0 (2009-02-09)
+------------------
+
+- added method to parse ISO 8601 time zone strings
+- added methods to create ISO 8601 conforming strings
+
+0.3.0 (2009-1-05)
+------------------
+
+- Initial release
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..127f5af
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+include CHANGES.txt
+include TODO.txt
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..9c789c2
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,103 @@
+
+ISO 8601 date/time parser
+=========================
+
+This module implements ISO 8601 date, time and duration parsing.
+The implementation follows ISO8601:2004 standard, and implements only
+date/time representations mentioned in the standard. If something is not
+mentioned there, then it is treated as non existent, and not as an allowed
+option.
+
+For instance, ISO8601:2004 never mentions 2 digit years. So, it is not
+intended by this module to support 2 digit years. (while it may still
+be valid as ISO date, because it is not explicitly forbidden.)
+Another example is, when no time zone information is given for a time,
+then it should be interpreted as local time, and not UTC.
+
+As this module maps ISO 8601 dates/times to standard Python data types, like
+*date*, *time*, *datetime* and *timedelta*, it is not possible to convert
+all possible ISO 8601 dates/times. For instance, dates before 0001-01-01 are
+not allowed by the Python *date* and *datetime* classes. Additionally
+fractional seconds are limited to microseconds. That means if the parser finds
+for instance nanoseconds it will round it to microseconds.
+
+Documentation
+-------------
+
+Currently there are four parsing methods available.
+   * parse_time:
+        parses an ISO 8601 time string into a *time* object
+   * parse_date:
+        parses an ISO 8601 date string into a *date* object
+   * parse_datetime:
+        parses an ISO 8601 date-time string into a *datetime* object
+   * parse_duration:
+        parses an ISO 8601 duration string into a *timedelta* or *Duration*
+        object.
+   * parse_tzinfo:
+        parses the time zone info part of an ISO 8601 string into a
+        *tzinfo* object.
+
+As ISO 8601 allows to define durations in years and months, and *timedelta*
+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
+*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*:
+
+    * time_isoformat:
+        Intended to create ISO time strings with default format
+        *hh:mm:ssZ*.
+    * date_isoformat:
+        Intended to create ISO date strings with default format
+        *yyyy-mm-dd*.
+    * datetime_isoformat:
+        Intended to create ISO date-time strings with default format
+        *yyyy-mm-ddThh:mm:ssZ*.
+    * duration_isoformat:
+        Intended to create ISO duration strings with default format
+        *PnnYnnMnnDTnnHnnMnnS*.
+    * tz_isoformat:
+        Intended to create ISO time zone strings with default format
+        *hh:mm*.
+    * strftime:
+        A re-implementation mostly compatible with Python's *strftime*, but
+        supports only those format strings, which can also be used for dates
+        prior 1900. This method also understands how to format *datetime* and
+        *Duration* instances.
+
+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:
+------------
+
+   * The parser accepts several date/time representation which should be invalid
+     according to ISO 8601 standard.
+
+     1. for date and time together, this parser accepts a mixture of basic and extended format.
+        e.g. the date could be in basic format, while the time is accepted in extended format.
+        It also allows short dates and times in date-time strings.
+     2. For incomplete dates, the first day is chosen. e.g. 19th century results in a date of
+        1901-01-01.
+     3. negative *Duration* and *timedelta* value are not fully supported yet.
+
+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.
+
+Source code is available at `<http://hg.proclos.com/isodate>`_.
+
diff --git a/TODO.txt b/TODO.txt
new file mode 100644
index 0000000..6843fa3
--- /dev/null
+++ b/TODO.txt
@@ -0,0 +1,39 @@
+
+TODOs
+=====
+
+This to do list contains some thoughts and ideas about missing features, and
+parts to think about, whether to implement them or not. This list is probably
+not complete.
+
+Missing features:
+-----------------
+
+    * time formating does not allow to create fractional representations.
+    * parser for ISO intervals.
+    * currently microseconds are always padded to a length of 6 characters.
+      trailing 0s should be optional
+
+Documentation:
+--------------
+
+    * parse_datetime:
+       - complete documentation to show what this function allows, but ISO forbids.
+         and vice verse.
+       - support other separators between date and time than 'T'
+
+    * parse_date:
+       - yeardigits should be always greater than 4
+       - dates before 0001-01-01 are not supported
+
+    * parse_duration:
+       - alternative formats are not fully supported due to parse_date restrictions
+       - standard duration format is fully supported but not very restrictive.
+
+    * Duration:
+       - support fractional years and month in calculations
+       - implement w3c order relation? (`<http://www.w3.org/TR/xmlschema-2/#duration-order>`_)
+       - refactor to have duration mathematics only at one place.
+       - localize __str__ method (does timedelta do this?)
+       - when is a Duration negative?
+       - normalize Durations. months [00-12] and years ]-inf,+inf[
diff --git a/bootstrap.py b/bootstrap.py
new file mode 100644
index 0000000..8cb88a1
--- /dev/null
+++ b/bootstrap.py
@@ -0,0 +1,62 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id$
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+try:
+    import pkg_resources
+except ImportError:
+    ez = {}
+    exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                         ).read() in ez
+    ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+    import pkg_resources
+
+if sys.platform == 'win32':
+    def quote(c):
+        if ' ' in c:
+            return '"%s"' % c # work around spawn lamosity on windows
+        else:
+            return c
+else:
+    def quote (c):
+        return c
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+ws  = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, quote (sys.executable),
+    '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)
diff --git a/buildout.cfg b/buildout.cfg
new file mode 100644
index 0000000..5f8c446
--- /dev/null
+++ b/buildout.cfg
@@ -0,0 +1,32 @@
+[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.py b/setup.py
new file mode 100644
index 0000000..21e5418
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,81 @@
+#!/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
+
+setupargs = {}
+
+try:
+    from distutils.command.build_py import build_py_2to3 as build_py
+except ImportError:
+    # 2.x
+    from distutils.command.build_py import build_py
+
+try:
+    from setuptools import setup
+    setupargs['test_suite'] = 'isodate.tests.test_suite'
+    setupargs['use_2to3'] = True
+except ImportError:
+    from distutils.core import setup
+    setupargs['cmdclass'] = {'build_py': build_py}
+
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+setup(name='isodate',
+      version='0.4.6',
+      packages=['isodate', 'isodate.tests'],
+      package_dir={'': 'src'},
+
+      # dependencies:
+      # install_requires = [],
+
+      # PyPI metadata
+      author='Gerhard Weis',
+      author_email='gerhard.weis at 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.txt') +
+                       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',
+                   'Programming Language :: Python :: 3',
+                   'Topic :: Internet',
+                   'Topic :: Software Development :: Libraries :: Python Modules',
+                   ],
+      **setupargs
+     )
diff --git a/src/isodate/__init__.py b/src/isodate/__init__.py
new file mode 100644
index 0000000..091af0a
--- /dev/null
+++ b/src/isodate/__init__.py
@@ -0,0 +1,55 @@
+##############################################################################
+# 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
+from isodate.isoduration import parse_duration, duration_isoformat, Duration
+from isodate.isoerror import ISO8601Error
+from isodate.isotzinfo import parse_tzinfo, tz_isoformat
+from isodate.tzinfo import UTC, FixedOffset, LOCAL
+from isodate.duration import Duration
+from isodate.isostrf import strftime
+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 TIME_BAS_COMPLETE, TIME_BAS_MINUTE
+from isodate.isostrf import TIME_EXT_COMPLETE, TIME_EXT_MINUTE
+from isodate.isostrf import TIME_HOUR
+from isodate.isostrf import TZ_BAS, TZ_EXT, TZ_HOUR
+from isodate.isostrf import DT_BAS_COMPLETE, DT_EXT_COMPLETE
+from isodate.isostrf import DT_BAS_ORD_COMPLETE, DT_EXT_ORD_COMPLETE
+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
diff --git a/src/isodate/duration.py b/src/isodate/duration.py
new file mode 100644
index 0000000..fa501cd
--- /dev/null
+++ b/src/isodate/duration.py
@@ -0,0 +1,261 @@
+##############################################################################
+# 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
+
+
+def fquotmod(val, low, high):
+    '''
+    A divmod function with boundaries.
+    '''
+    div, mod = divmod(val - low, high - low)
+    mod += low
+    return int(div), mod
+
+
+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):
+        return 30
+    if ((year % 400) == 0) or ((year % 100) != 0) and ((year % 4) == 0):
+        return 29
+    return 28
+
+
+class Duration(object):
+    '''
+    A class which represents a duration.
+
+    The difference to datetime.timedelta is, that this class handles also
+    differences given in years and months.
+    A Duration treats differences given in year, months separately from all
+    other components.
+
+    A Duration can be used almost like any timedelta object, however there
+    are some restrictions:
+      * It is not really possible to compare Durations, because it is unclear,
+        whether a duration of 1 year is bigger than 365 days or not.
+      * Equality is only tested between the two (year, month vs. timedelta)
+        basic components.
+
+    A Duration can also be converted into a datetime object, but this requires
+    a start date or an end date.
+
+    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):
+        '''
+        Initialise this Duration instance with the given parameters.
+        '''
+        self.months = months
+        self.years = years
+        self.tdelta = timedelta(days, seconds, microseconds, milliseconds,
+                                minutes, hours, weeks)
+
+    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)
+        if self.months:
+            params.append('%d months' % self.months)
+        params.append(str(self.tdelta))
+        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)
+
+    def __neg__(self):
+        """
+        A simple unary minus.
+
+        Returns a new Duration instance with all it's negated.
+        """
+        negduration = Duration(years=-self.years, months=-self.months)
+        negduration.tdelta = -self.tdelta
+        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.tdelta = self.tdelta + other.tdelta
+            return newduration
+        if isinstance(other, (date, datetime)):
+            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 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):
+            newduration = Duration(years=self.years, months=self.months)
+            newduration.tdelta = self.tdelta + other
+            return newduration
+        if isinstance(other, (date, datetime)):
+            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__))
+
+    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.tdelta = self.tdelta - other.tdelta
+            return newduration
+        if isinstance(other, timedelta):
+            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__))
+
+    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)):
+            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
+        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__))
+
+    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):
+            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:
+            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
+
+    def todatetime(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:
+            raise ValueError("only start or end allowed")
+        if start is not None:
+            return (start + self) - start
+        return end - (end - self)
diff --git a/src/isodate/isodates.py b/src/isodate/isodates.py
new file mode 100644
index 0000000..8bafa20
--- /dev/null
+++ b/src/isodate/isodates.py
@@ -0,0 +1,201 @@
+##############################################################################
+# 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
+
+from isodate.isostrf import strftime, DATE_EXT_COMPLETE
+from isodate.isoerror import ISO8601Error
+
+DATE_REGEX_CACHE = {}
+# A dictionary to cache pre-compiled regular expressions.
+# A set of regular expressions is identified, by number of year digits allowed
+# and whether a plus/minus sign is required or not. (This option is changeable
+# only for 4 digit years).  
+
+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.
+    
+    It is necessary to fix the number of year digits, else it is not possible
+    to automatically distinguish between various ISO date formats.
+    
+    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:
+        cache_entry = []
+        # ISO 8601 expanded DATE formats allow an arbitrary number of year
+        # digits with a leading +/- sign.
+        if expanded:
+            sign = 1
+        else:
+            sign = 0
+        # 1. complete dates:
+        #    YYYY-MM-DD or +- YYYYYY-MM-DD... extended date format
+        cache_entry.append(re.compile(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
+        cache_entry.append(re.compile(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
+        cache_entry.append(re.compile(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
+        cache_entry.append(re.compile(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
+        cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
+                                      r"-(?P<day>[0-9]{3})"
+                                      % (sign, yeardigits)))
+        #    YYYYDDD or +-YYYYYYDDD ... basic format
+        cache_entry.append(re.compile(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
+        cache_entry.append(re.compile(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 
+        cache_entry.append(re.compile(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
+        cache_entry.append(re.compile(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
+        cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})"
+                                      % (sign, yeardigits)))
+        # 7. century dates:
+        #    YY or +-YYYY ... reduced accuracy specific century
+        cache_entry.append(re.compile(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):
+    '''
+    Parse an ISO 8601 date string into a datetime.date object.
+    
+    As the datetime.date implementation is limited to dates starting from
+    0001-01-01, negative dates (BC) and year 0 can not be parsed by this
+    method.
+    
+    For incomplete dates, this method chooses the first day for it. For
+    instance if only a century is given, this method returns the 1st of 
+    January in year 1 of this century.
+    
+    supported formats: (expanded formats are shown with 6 digits for year)
+      YYYYMMDD    +-YYYYYYMMDD      basic complete date
+      YYYY-MM-DD  +-YYYYYY-MM-DD    extended complete date
+      YYYYWwwD    +-YYYYYYWwwD      basic complete week date
+      YYYY-Www-D  +-YYYYYY-Www-D    extended complete week date
+      YYYYDDD     +-YYYYYYDDD       basic ordinal date
+      YYYY-DDD    +-YYYYYY-DDD      extended ordinal date
+      YYYYWww     +-YYYYYYWww       basic incomplete week date
+      YYYY-Www    +-YYYYYY-Www      extended incomplete week date
+      YYY-MM      +-YYYYYY-MM       incomplete month date
+      YYYY        +-YYYYYY          incomplete year date
+      YY          +-YYYY            incomplete century date
+
+    @param datestring: the ISO date string to parse
+    @param yeardigits: how many digits are used to represent a year
+    @param expanded: if True then +/- signs are allowed. This parameter
+                     is forced to True, if yeardigits != 4
+                     
+    @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)
+    for pattern in isodates:
+        match = pattern.match(datestring)
+        if match:
+            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 not 'month' 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)
+                    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)
+                else:  # year date
+                    return ret
+            # year-, month-, or complete date
+            if 'day' not in groups or groups['day'] is None:
+                day = 1
+            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)
+
+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
new file mode 100644
index 0000000..7e4d570
--- /dev/null
+++ b/src/isodate/isodatetime.py
@@ -0,0 +1,61 @@
+##############################################################################
+# 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
+from isodate.isostrf import DATE_EXT_COMPLETE, TIME_EXT_COMPLETE, TZ_EXT
+from isodate.isodates import parse_date
+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.
+    '''
+    datestring, timestring = datetimestring.split('T')
+    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):
+    '''
+    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
new file mode 100644
index 0000000..3554b1f
--- /dev/null
+++ b/src/isodate/isoduration.py
@@ -0,0 +1,140 @@
+##############################################################################
+# 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
+import re
+
+from isodate.duration import Duration
+from isodate.isoerror import ISO8601Error
+from isodate.isodatetime import parse_datetime
+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<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)?)?$")
+# regular expression to parse ISO duartion strings.
+
+
+def parse_duration(datestring):
+    """
+    Parses an ISO 8601 durations into datetime.timedelta or Duration objects.
+
+    If the ISO date string does not contain years or months, a timedelta
+    instance is returned, else a Duration instance is returned.
+
+    The following duration formats are supported:
+      -PnnW                  duration in weeks
+      -PnnYnnMnnDTnnHnnMnnS  complete duration specification
+      -PYYYYMMDDThhmmss      basic alternative complete date format
+      -PYYYY-MM-DDThh:mm:ss  extended alternative complete date format
+      -PYYYYDDDThhmmss       basic alternative ordinal date format
+      -PYYYY-DDDThh:mm:ss    extended alternative ordinal date format
+
+    The '-' is optional.
+
+    Limitations:  ISO standard defines some restrictions about where to use
+      fractional numbers and which component and format combinations are
+      allowed. This parser implementation ignores all those restrictions and
+      returns something when it is able to find all necessary components.
+      In detail:
+        it does not check, whether only the last component has fractions.
+        it allows weeks specified with all other combinations
+
+      The alternative format does not support durations with years, months or
+      days set to 0.
+    """
+    if not isinstance(datestring, basestring):
+        raise TypeError("Expecting a string %r" % datestring)
+    match = ISO8601_PERIOD_REGEX.match(datestring)
+    if not match:
+        # try alternative format:
+        if datestring.startswith("P"):
+            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)
+            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)
+            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 val is None:
+                groups[key] = "0n"
+            #print groups[key]
+            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(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(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 = '-'
+    else:
+        ret = ''
+    ret += strftime(tduration, format)
+    return ret
diff --git a/src/isodate/isoerror.py b/src/isodate/isoerror.py
new file mode 100644
index 0000000..b690bfe
--- /dev/null
+++ b/src/isodate/isoerror.py
@@ -0,0 +1,32 @@
+##############################################################################
+# 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(Exception):
+    '''Raised when the given ISO string can not be parsed.'''
diff --git a/src/isodate/isostrf.py b/src/isodate/isostrf.py
new file mode 100644
index 0000000..e492a39
--- /dev/null
+++ b/src/isodate/isostrf.py
@@ -0,0 +1,207 @@
+##############################################################################
+# 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.
+
+The strftime method in this module allows only a subset of Python's strftime
+format codes, plus a few additional. It supports the full range of date values
+possible with standard Python date/time objects. Furthermore there are several
+pr-defined format strings in this module to make ease producing of ISO 8601
+conforming strings.
+"""
+import re
+from datetime import date, timedelta
+
+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'
+
+# 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 zone formats
+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
+
+# 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: '%d' % 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.tzinfo, '%h'),
+               '%Z': lambda tdt, yds: tz_isoformat(tdt.tzinfo, '%Z'),
+               '%z': lambda tdt, yds: tz_isoformat(tdt.tzinfo, '%z'),
+               '%%': lambda tdt, yds: '%'}
+
+STRF_D_MAP = {'%d': lambda tdt, yds: '%02d' % tdt.days,
+              '%f': lambda tdt, yds: '%d' % 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':
+            ret = []
+            if isinstance(tdt, Duration):
+                if 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)
+            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)
+            if hours or minutes or seconds or usecs:
+                ret.append('T')
+                if hours:
+                    ret.append('%sH' % hours)
+                if minutes:
+                    ret.append('%sM' % minutes)
+                if seconds or usecs:
+                    if usecs:
+                        ret.append(("%d.%06d" % (seconds, usecs)).rstrip('0'))
+                    else:
+                        ret.append("%d" % seconds)
+                    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 match.group(0)
+    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)
+
+
+def strftime(tdt, format, yeardigits=4):
+    '''
+    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)
+    %H    Hour (24-hour clock) as a decimal number [00,23].
+    %j    Day of the year as a decimal number [001,366].
+    %m    Month as a decimal number [01,12].
+    %M    Minute as a decimal number [00,59].
+    %S    Second as a decimal number [00,61].    (3)
+    %w    Weekday as a decimal number [0(Monday),6].
+    %W    Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0.    (4)
+    %Y    Year with century as a decimal number. [0000,9999]
+    %C    Century as a decimal number. [00,99]
+    %z    UTC offset in the form +HHMM or -HHMM (empty string if the the object is naive).    (5)
+    %Z    Time zone name (empty string if the object is naive).
+    %P    ISO8601 duration format.
+    %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
new file mode 100644
index 0000000..f0003d6
--- /dev/null
+++ b/src/isodate/isotime.py
@@ -0,0 +1,156 @@
+##############################################################################
+# 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
+
+from isodate.isostrf import strftime, TIME_EXT_COMPLETE, TZ_EXT
+from isodate.isoerror import ISO8601Error
+from isodate.isotzinfo import TZ_REGEX, build_tzinfo
+
+TIME_REGEX_CACHE = []
+# used to cache regular expressions to parse ISO time strings.
+
+
+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
+        #    fraction separators.
+        #    The letter 'T' is allowed as time designator in front of a time
+        #    expression.
+        #    Immediately after a time expression, a time zone definition is
+        #      allowed.
+        #    a TZ may be missing (local time), be a 'Z' for UTC or a string of
+        #    +-hh:mm where the ':mm' part can be skipped.
+        # TZ information patterns:
+        #    ''
+        #    Z
+        #    +-hh:mm
+        #    +-hhmm
+        #    +-hh =>
+        #    isotzinfo.TZ_REGEX
+        # 1. complete time:
+        #    hh:mm:ss.ss ... extended format
+        TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2}):"
+                                           r"(?P<minute>[0-9]{2}):"
+                                           r"(?P<second>[0-9]{2}([,.][0-9]+)?)"
+                                           + TZ_REGEX))
+        #    hhmmss.ss ... basic format
+        TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2})"
+                                           r"(?P<minute>[0-9]{2})"
+                                           r"(?P<second>[0-9]{2}([,.][0-9]+)?)"
+                                           + TZ_REGEX))
+        # 2. reduced accuracy:
+        #    hh:mm.mm ... extended format
+        TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2}):"
+                                           r"(?P<minute>[0-9]{2}([,.][0-9]+)?)"
+                                           + TZ_REGEX))
+        #    hhmm.mm ... basic format
+        TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2})"
+                                           r"(?P<minute>[0-9]{2}([,.][0-9]+)?)"
+                                           + TZ_REGEX))
+        #    hh.hh ... basic format
+        TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2}([,.][0-9]+)?)"
+                                           + TZ_REGEX))
+    return TIME_REGEX_CACHE
+
+
+def parse_time(timestring):
+    '''
+    Parses ISO 8601 times into datetime.time objects.
+
+    Following ISO 8601 formats are supported:
+      (as decimal separator a ',' or a '.' is allowed)
+      hhmmss.ssTZD    basic complete time
+      hh:mm:ss.ssTZD  extended compelte time
+      hhmm.mmTZD      basic reduced accuracy time
+      hh:mm.mmTZD     extended reduced accuracy time
+      hh.hhTZD        basic reduced accuracy time
+    TZD is the time zone designator which can be in the following format:
+              no designator indicates local time zone
+      Z       UTC
+      +-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)
+        if match:
+            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:
+                second = Decimal(groups['second'])
+                microsecond = (second - int(second)) * long(1e6)
+                # int(...) ... no rounding
+                # to_integral() ... rounding
+                return time(int(groups['hour']), int(groups['minute']),
+                            int(second), 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),
+                            microsecond.to_integral(), tzinfo)
+            else:
+                microsecond, second, minute = 0, 0, 0
+            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),
+                        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
new file mode 100644
index 0000000..79797fa
--- /dev/null
+++ b/src/isodate/isotzinfo.py
@@ -0,0 +1,108 @@
+##############################################################################
+# 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_RE = re.compile(TZ_REGEX)
+
+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 == '':
+        return None
+    if tzname == 'Z':
+        return UTC
+    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:
+              no designator indicates local time zone
+      Z       UTC
+      +-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)
+
+def tz_isoformat(tzinfo, format='%Z'):
+    '''
+    return time zone offset ISO 8601 formatted. 
+    The various ISO formats can be chosen with the format parameter.
+    
+    if tzinfo is None returns ''
+    if tzinfo is UTC returns 'Z'
+    else the offset is rendered to the given format. 
+    format:
+        %h ... +-HH
+        %z ... +-HHMM
+        %Z ... +-HH:MM
+    '''
+    if (tzinfo is None) or (tzinfo.utcoffset(None) is None):
+        return ''
+    if tzinfo.utcoffset(None) == ZERO and tzinfo.dst(None) == ZERO:
+        return 'Z'
+    tdelta = tzinfo.utcoffset(None)
+    seconds = tdelta.days * 24 * 60 * 60 + tdelta.seconds
+    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 AttributeError('unknown format string "%s"' % format)
diff --git a/src/isodate/tests/__init__.py b/src/isodate/tests/__init__.py
new file mode 100644
index 0000000..67512d0
--- /dev/null
+++ b/src/isodate/tests/__init__.py
@@ -0,0 +1,45 @@
+##############################################################################
+# 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
+
+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()])
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
diff --git a/src/isodate/tests/test_date.py b/src/isodate/tests/test_date.py
new file mode 100644
index 0000000..fb6ba8c
--- /dev/null
+++ b/src/isodate/tests/test_date.py
@@ -0,0 +1,122 @@
+##############################################################################
+# 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_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
+from isodate import DATE_EXT_WEEK, DATE_EXT_WEEK_COMPLETE
+
+# the following list contains tuples of ISO date strings and the expected
+# 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)]}
+
+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)
+            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)
+            else:
+                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))
+    return 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
new file mode 100644
index 0000000..fd50039
--- /dev/null
+++ b/src/isodate/tests/test_datetime.py
@@ -0,0 +1,110 @@
+##############################################################################
+# 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
+
+from isodate import parse_datetime, UTC, FixedOffset, datetime_isoformat
+from isodate import DATE_BAS_COMPLETE, TIME_BAS_MINUTE, TIME_BAS_COMPLETE
+from isodate import DATE_EXT_COMPLETE, TIME_EXT_MINUTE
+from isodate import TZ_BAS, TZ_EXT, TZ_HOUR
+from isodate import DATE_BAS_ORD_COMPLETE, DATE_EXT_ORD_COMPLETE
+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),
+              ('1985-04-12T10:15', datetime(1985, 4, 12, 10, 15),
+               DATE_EXT_COMPLETE + 'T' + TIME_EXT_MINUTE),
+              ('1985102T1015Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC),
+               DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS),
+              ('1985-102T10:15Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC),
+               DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_EXT),
+              ('1985W155T1015+0400', datetime(1985, 4, 12, 10, 15,
+                                              tzinfo=FixedOffset(4, 0,
+                                                                 '+0400')),
+               DATE_BAS_WEEK_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS),
+              ('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),
+              ('20110410T101225.123000Z',
+               datetime(2011, 4, 10, 10, 12, 25, 123000, tzinfo=UTC),
+               DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE + ".%f" + TZ_BAS)]
+
+
+def create_testcase(datetimestring, 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 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.
+            '''
+            result = parse_datetime(datetimestring)
+            self.assertEqual(result, 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)
+            else:
+                self.assertEqual(datetime_isoformat(expectation, format),
+                                 datetimestring)
+
+    return unittest.TestLoader().loadTestsFromTestCase(TestDateTime)
+
+
+def test_suite():
+    '''
+    Construct a TestSuite instance for all test cases.
+    '''
+    suite = unittest.TestSuite()
+    for datetimestring, expectation, format in TEST_CASES:
+        suite.addTest(create_testcase(datetimestring, expectation, format))
+    return 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
new file mode 100644
index 0000000..d8b4cf2
--- /dev/null
+++ b/src/isodate/tests/test_duration.py
@@ -0,0 +1,479 @@
+##############################################################################
+# 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
+
+from isodate import Duration, parse_duration, ISO8601Error
+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),
+                    }
+
+#                       d1                    d2           '+', '-', '>'
+# A list of test cases to test addition and subtraction between datetime and
+# Duration objects.
+# 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))
+
+# 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)),
+                     )
+
+# 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)),
+                    (Duration(years=1, months=1, weeks=5),
+                     'raise exception',
+                     None),
+                    ('raise exception',
+                     Duration(years=1, months=1, weeks=5),
+                     None),
+                    (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)),
+                    #(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)),
+                     )
+
+
+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)
+        res1 = start + days1 + months1
+        res2 = start + months1 + days1
+        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))
+
+    def test_parseerror(self):
+        '''
+        Test for unparseable duration string.
+        '''
+        self.assertRaises(ISO8601Error, parse_duration, 'T10:10:10')
+
+    def test_repr(self):
+        '''
+        Test __repr__ and __str__ for Duration obqects.
+        '''
+        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))
+
+    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(months=-13))
+        self.assertNotEqual(-Duration(years=1), timedelta(days=-365))
+        self.assertNotEqual(-timedelta(days=365), Duration(years=-1))
+        # FIXME: this test fails in python 3... it seems like python3
+        #        treats a == b the same b == a
+        #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')
+
+
+    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(months=13))
+        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(months=13))
+        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))
+        # FIXME: this test fails in python 3... it seems like python3
+        #        treats a != b the same b != a
+        #self.assertNotEqual(timedelta(days=1), Duration(days=1))
+
+
+def create_parsetestcase(durationstring, expectation, format, altstr):
+    """
+    Create a TestCase class for a specific test.
+
+    This allows having a separate TestCase for each test tuple from the
+    PARSE_TEST_CASES list, so that a failed test won't stop other tests.
+    """
+
+    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)
+            else:
+                # if durationstring == '-P2W':
+                #     import pdb; pdb.set_trace()
+                self.assertEqual(duration_isoformat(expectation, format),
+                                 durationstring)
+
+    return unittest.TestLoader().loadTestsFromTestCase(TestParseDuration)
+
+
+def create_mathtestcase(dur1, dur2, resadd, ressub, resge):
+    """
+    Create a TestCase class for a specific test.
+
+    This allows having a separate TestCase for each test tuple from the
+    MATH_TEST_CASES list, so that a failed test won't stop other tests.
+    """
+
+    dur1 = parse_duration(dur1)
+    dur2 = parse_duration(dur2)
+    resadd = parse_duration(resadd)
+    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.'''
+                return dur1 > dur2
+
+            def doletest():
+                ''' Test less than.'''
+                return dur1 < dur2
+            if resge is None:
+                self.assertRaises(TypeError, dogetest)
+                self.assertRaises(TypeError, doletest)
+            else:
+                self.assertEqual(dogetest(), resge)
+                self.assertEqual(doletest(), not resge)
+
+    return unittest.TestLoader().loadTestsFromTestCase(TestMathDuration)
+
+
+def create_datetestcase(start, tdelta, duration):
+    """
+    Create a TestCase class for a specific test.
+
+    This allows having a separate TestCase for each test tuple from the
+    DATE_TEST_CASES list, so that a failed test won't stop other tests.
+    """
+
+    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)
+
+
+def create_datecalctestcase(start, duration, 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 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(TypeError, operator.add, start, duration)
+            else:
+                self.assertEqual(start + duration, expectation)
+
+    return unittest.TestLoader().loadTestsFromTestCase(TestDateCalc)
+
+
+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 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))
+    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(DurationTest))
+    return 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
new file mode 100644
index 0000000..d71a3e2
--- /dev/null
+++ b/src/isodate/tests/test_time.py
@@ -0,0 +1,139 @@
+##############################################################################
+# 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
+
+from isodate import parse_time, UTC, FixedOffset, ISO8601Error, time_isoformat
+from isodate import TIME_BAS_COMPLETE, TIME_BAS_MINUTE
+from isodate import TIME_EXT_COMPLETE, TIME_EXT_MINUTE
+from isodate import TIME_HOUR
+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)]
+
+
+def create_testcase(timestring, 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 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:
+                result = parse_time(timestring)
+                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)
+            elif format is not None:
+                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))
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
diff --git a/src/isodate/tzinfo.py b/src/isodate/tzinfo.py
new file mode 100644
index 0000000..776d905
--- /dev/null
+++ b/src/isodate/tzinfo.py
@@ -0,0 +1,137 @@
+'''
+This module provides some datetime.tzinfo implementations. 
+
+All those classes are taken from the Python documentation.
+'''
+from datetime import timedelta, tzinfo
+import time
+
+ZERO = timedelta(0)
+# constant for zero time offset.    
+
+class Utc(tzinfo):
+    '''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
+
+UTC = Utc()
+# the default instance for UTC.
+
+class FixedOffset(tzinfo):
+    '''
+    A class building tzinfo objects for fixed-offset time zones.
+    
+    Note that FixedOffset(0, "UTC") is a different way to build a
+    UTC tzinfo object.
+    '''
+    
+    def __init__(self, offset_hours, offset_minutes, name):
+        '''
+        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
+
+
+STDOFFSET = timedelta(seconds = -time.timezone)
+# locale time zone offset
+
+# calculate local daylight saving offset if any.
+if time.daylight:
+    DSTOFFSET = timedelta(seconds = -time.altzone)
+else:
+    DSTOFFSET = STDOFFSET
+
+DSTDIFF = DSTOFFSET - STDOFFSET
+# difference between local time zone and local DST time zone
+
+class LocalTimezone(tzinfo):
+    '''
+    A class capturing the platform's idea of local time.
+    '''
+
+    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)
+        stamp = time.mktime(tt)
+        tt = time.localtime(stamp)
+        return tt.tm_isdst > 0
+
+LOCAL = LocalTimezone()
+# the default instance for local time zone.

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/isodate.git



More information about the Python-modules-commits mailing list