Import Upstream version 0.5.4
This commit is contained in:
commit
45a2b87a8f
|
@ -0,0 +1,92 @@
|
|||
|
||||
CHANGES
|
||||
=======
|
||||
|
||||
0.5.4 (2015-08-06)
|
||||
------------------
|
||||
|
||||
- Fix parsing of Periods (Fabien Bochu)
|
||||
- Make Duration objects hashable (Geoffrey Fairchild)
|
||||
- Add multiplication to duration (Reinoud Elhorst)
|
||||
|
||||
|
||||
0.5.1 (2014-11-07)
|
||||
------------------
|
||||
|
||||
- fixed pickling of Duration objects
|
||||
- raise ISO8601Error when there is no 'T' separator in datetime strings (Adrian Coveney)
|
||||
|
||||
|
||||
0.5.0 (2014-02-23)
|
||||
------------------
|
||||
|
||||
- ISO8601Error are subclasses of ValueError now (Michael Hrivnak)
|
||||
- improve compatibility across various python variants and versions
|
||||
- raise exceptions when using fractional years and months in date
|
||||
maths with durations
|
||||
- renamed method todatetime on Duraction objects to totimedelta
|
||||
|
||||
|
||||
0.4.9 (2012-10-30)
|
||||
------------------
|
||||
|
||||
- support pickling FixedOffset instances
|
||||
- make sure parsed fractional seconds are in microseconds
|
||||
- add leading zeros when formattig microseconds (Jarom Loveridge)
|
||||
|
||||
|
||||
0.4.8 (2012-05-04)
|
||||
------------------
|
||||
|
||||
- fixed incompatibility of unittests with python 2.5 and 2.6 (runs fine on 2.7
|
||||
and 3.2)
|
||||
|
||||
|
||||
0.4.7 (2012-01-26)
|
||||
------------------
|
||||
|
||||
- fixed tzinfo formatting (never pass None into tzinfo.utcoffset())
|
||||
|
||||
|
||||
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
|
|
@ -0,0 +1,2 @@
|
|||
include CHANGES.txt
|
||||
include TODO.txt
|
|
@ -0,0 +1,273 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: isodate
|
||||
Version: 0.5.4
|
||||
Summary: An ISO 8601 date/time/duration parser and formatter
|
||||
Home-page: http://cheeseshop.python.org/pypi/isodate
|
||||
Author: Gerhard Weis
|
||||
Author-email: gerhard.weis@proclos.com
|
||||
License: BSD
|
||||
Description:
|
||||
ISO 8601 date/time parser
|
||||
=========================
|
||||
|
||||
.. image:: https://travis-ci.org/gweis/isodate.svg?branch=master
|
||||
:target: https://travis-ci.org/gweis/isodate
|
||||
:alt: Travis-CI
|
||||
.. image:: https://coveralls.io/repos/gweis/isodate/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/gweis/isodate?branch=master
|
||||
:alt: Coveralls
|
||||
.. image:: https://pypip.in/version/isodate/badge.svg
|
||||
:target: https://pypi.python.org/pypi/isodate/
|
||||
:alt: Latest Version
|
||||
.. image:: https://pypip.in/download/isodate/badge.svg
|
||||
:target: https://pypi.python.org/pypi/isodate/
|
||||
:alt: Downloads
|
||||
.. image:: https://pypip.in/license/isodate/badge.svg
|
||||
:target: https://pypi.python.org/pypi/isodate/
|
||||
:alt: License
|
||||
|
||||
|
||||
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 formatting methods for all supported data types. Each
|
||||
*xxx_isoformat* method accepts a format parameter. The default format is
|
||||
always the ISO 8601 expanded format. This is the same format used by
|
||||
*datetime.isoformat*:
|
||||
|
||||
* 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://github.com/gweis/isodate>`_.
|
||||
|
||||
CHANGES
|
||||
=======
|
||||
|
||||
0.5.4 (2015-08-06)
|
||||
------------------
|
||||
|
||||
- Fix parsing of Periods (Fabien Bochu)
|
||||
- Make Duration objects hashable (Geoffrey Fairchild)
|
||||
- Add multiplication to duration (Reinoud Elhorst)
|
||||
|
||||
|
||||
0.5.1 (2014-11-07)
|
||||
------------------
|
||||
|
||||
- fixed pickling of Duration objects
|
||||
- raise ISO8601Error when there is no 'T' separator in datetime strings (Adrian Coveney)
|
||||
|
||||
|
||||
0.5.0 (2014-02-23)
|
||||
------------------
|
||||
|
||||
- ISO8601Error are subclasses of ValueError now (Michael Hrivnak)
|
||||
- improve compatibility across various python variants and versions
|
||||
- raise exceptions when using fractional years and months in date
|
||||
maths with durations
|
||||
- renamed method todatetime on Duraction objects to totimedelta
|
||||
|
||||
|
||||
0.4.9 (2012-10-30)
|
||||
------------------
|
||||
|
||||
- support pickling FixedOffset instances
|
||||
- make sure parsed fractional seconds are in microseconds
|
||||
- add leading zeros when formattig microseconds (Jarom Loveridge)
|
||||
|
||||
|
||||
0.4.8 (2012-05-04)
|
||||
------------------
|
||||
|
||||
- fixed incompatibility of unittests with python 2.5 and 2.6 (runs fine on 2.7
|
||||
and 3.2)
|
||||
|
||||
|
||||
0.4.7 (2012-01-26)
|
||||
------------------
|
||||
|
||||
- fixed tzinfo formatting (never pass None into tzinfo.utcoffset())
|
||||
|
||||
|
||||
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
|
||||
|
||||
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[
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Internet
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
@ -0,0 +1,119 @@
|
|||
|
||||
ISO 8601 date/time parser
|
||||
=========================
|
||||
|
||||
.. image:: https://travis-ci.org/gweis/isodate.svg?branch=master
|
||||
:target: https://travis-ci.org/gweis/isodate
|
||||
:alt: Travis-CI
|
||||
.. image:: https://coveralls.io/repos/gweis/isodate/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/gweis/isodate?branch=master
|
||||
:alt: Coveralls
|
||||
.. image:: https://pypip.in/version/isodate/badge.svg
|
||||
:target: https://pypi.python.org/pypi/isodate/
|
||||
:alt: Latest Version
|
||||
.. image:: https://pypip.in/download/isodate/badge.svg
|
||||
:target: https://pypi.python.org/pypi/isodate/
|
||||
:alt: Downloads
|
||||
.. image:: https://pypip.in/license/isodate/badge.svg
|
||||
:target: https://pypi.python.org/pypi/isodate/
|
||||
:alt: License
|
||||
|
||||
|
||||
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 formatting methods for all supported data types. Each
|
||||
*xxx_isoformat* method accepts a format parameter. The default format is
|
||||
always the ISO 8601 expanded format. This is the same format used by
|
||||
*datetime.isoformat*:
|
||||
|
||||
* 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://github.com/gweis/isodate>`_.
|
|
@ -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[
|
|
@ -0,0 +1,5 @@
|
|||
[egg_info]
|
||||
tag_build =
|
||||
tag_svn_revision = 0
|
||||
tag_date = 0
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
#!/usr/bin/env python
|
||||
##############################################################################
|
||||
# Copyright 2009, Gerhard Weis
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of the authors nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||
##############################################################################
|
||||
import os
|
||||
import sys
|
||||
|
||||
setupargs = {}
|
||||
|
||||
try:
|
||||
from setuptools import setup
|
||||
setupargs['test_suite'] = 'isodate.tests.test_suite'
|
||||
if sys.version[0] == '3':
|
||||
setupargs['use_2to3'] = True
|
||||
except ImportError:
|
||||
from distutils.core import setup
|
||||
if sys.version[0] == '3':
|
||||
from distutils.command.build_py import build_py_2to3
|
||||
setupargs['cmdclass'] = {'build_py': build_py_2to3}
|
||||
|
||||
|
||||
def read(*rnames):
|
||||
return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
|
||||
|
||||
setup(name='isodate',
|
||||
version='0.5.4',
|
||||
packages=['isodate', 'isodate.tests'],
|
||||
package_dir={'': 'src'},
|
||||
|
||||
# dependencies:
|
||||
# install_requires = [],
|
||||
|
||||
# PyPI metadata
|
||||
author='Gerhard Weis',
|
||||
author_email='gerhard.weis@proclos.com',
|
||||
description='An ISO 8601 date/time/duration parser and formatter',
|
||||
license='BSD',
|
||||
# keywords = '',
|
||||
url='http://cheeseshop.python.org/pypi/isodate',
|
||||
|
||||
long_description=(read('README.rst') +
|
||||
read('CHANGES.txt') +
|
||||
read('TODO.txt')),
|
||||
|
||||
classifiers=['Development Status :: 4 - Beta',
|
||||
# 'Environment :: Web Environment',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
'Topic :: Internet',
|
||||
('Topic :: Software Development :'
|
||||
': Libraries :: Python Modules'),
|
||||
],
|
||||
**setupargs)
|
|
@ -0,0 +1,273 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: isodate
|
||||
Version: 0.5.4
|
||||
Summary: An ISO 8601 date/time/duration parser and formatter
|
||||
Home-page: http://cheeseshop.python.org/pypi/isodate
|
||||
Author: Gerhard Weis
|
||||
Author-email: gerhard.weis@proclos.com
|
||||
License: BSD
|
||||
Description:
|
||||
ISO 8601 date/time parser
|
||||
=========================
|
||||
|
||||
.. image:: https://travis-ci.org/gweis/isodate.svg?branch=master
|
||||
:target: https://travis-ci.org/gweis/isodate
|
||||
:alt: Travis-CI
|
||||
.. image:: https://coveralls.io/repos/gweis/isodate/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/gweis/isodate?branch=master
|
||||
:alt: Coveralls
|
||||
.. image:: https://pypip.in/version/isodate/badge.svg
|
||||
:target: https://pypi.python.org/pypi/isodate/
|
||||
:alt: Latest Version
|
||||
.. image:: https://pypip.in/download/isodate/badge.svg
|
||||
:target: https://pypi.python.org/pypi/isodate/
|
||||
:alt: Downloads
|
||||
.. image:: https://pypip.in/license/isodate/badge.svg
|
||||
:target: https://pypi.python.org/pypi/isodate/
|
||||
:alt: License
|
||||
|
||||
|
||||
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 formatting methods for all supported data types. Each
|
||||
*xxx_isoformat* method accepts a format parameter. The default format is
|
||||
always the ISO 8601 expanded format. This is the same format used by
|
||||
*datetime.isoformat*:
|
||||
|
||||
* 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://github.com/gweis/isodate>`_.
|
||||
|
||||
CHANGES
|
||||
=======
|
||||
|
||||
0.5.4 (2015-08-06)
|
||||
------------------
|
||||
|
||||
- Fix parsing of Periods (Fabien Bochu)
|
||||
- Make Duration objects hashable (Geoffrey Fairchild)
|
||||
- Add multiplication to duration (Reinoud Elhorst)
|
||||
|
||||
|
||||
0.5.1 (2014-11-07)
|
||||
------------------
|
||||
|
||||
- fixed pickling of Duration objects
|
||||
- raise ISO8601Error when there is no 'T' separator in datetime strings (Adrian Coveney)
|
||||
|
||||
|
||||
0.5.0 (2014-02-23)
|
||||
------------------
|
||||
|
||||
- ISO8601Error are subclasses of ValueError now (Michael Hrivnak)
|
||||
- improve compatibility across various python variants and versions
|
||||
- raise exceptions when using fractional years and months in date
|
||||
maths with durations
|
||||
- renamed method todatetime on Duraction objects to totimedelta
|
||||
|
||||
|
||||
0.4.9 (2012-10-30)
|
||||
------------------
|
||||
|
||||
- support pickling FixedOffset instances
|
||||
- make sure parsed fractional seconds are in microseconds
|
||||
- add leading zeros when formattig microseconds (Jarom Loveridge)
|
||||
|
||||
|
||||
0.4.8 (2012-05-04)
|
||||
------------------
|
||||
|
||||
- fixed incompatibility of unittests with python 2.5 and 2.6 (runs fine on 2.7
|
||||
and 3.2)
|
||||
|
||||
|
||||
0.4.7 (2012-01-26)
|
||||
------------------
|
||||
|
||||
- fixed tzinfo formatting (never pass None into tzinfo.utcoffset())
|
||||
|
||||
|
||||
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
|
||||
|
||||
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[
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Internet
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
@ -0,0 +1,26 @@
|
|||
CHANGES.txt
|
||||
MANIFEST.in
|
||||
README.rst
|
||||
TODO.txt
|
||||
setup.py
|
||||
src/isodate/__init__.py
|
||||
src/isodate/duration.py
|
||||
src/isodate/isodates.py
|
||||
src/isodate/isodatetime.py
|
||||
src/isodate/isoduration.py
|
||||
src/isodate/isoerror.py
|
||||
src/isodate/isostrf.py
|
||||
src/isodate/isotime.py
|
||||
src/isodate/isotzinfo.py
|
||||
src/isodate/tzinfo.py
|
||||
src/isodate.egg-info/PKG-INFO
|
||||
src/isodate.egg-info/SOURCES.txt
|
||||
src/isodate.egg-info/dependency_links.txt
|
||||
src/isodate.egg-info/top_level.txt
|
||||
src/isodate/tests/__init__.py
|
||||
src/isodate/tests/test_date.py
|
||||
src/isodate/tests/test_datetime.py
|
||||
src/isodate/tests/test_duration.py
|
||||
src/isodate/tests/test_pickle.py
|
||||
src/isodate/tests/test_strf.py
|
||||
src/isodate/tests/test_time.py
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
isodate
|
|
@ -0,0 +1,70 @@
|
|||
##############################################################################
|
||||
# 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
|
||||
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
|
||||
|
||||
__all__ = ['parse_date', 'date_isoformat', 'parse_time', 'time_isoformat',
|
||||
'parse_datetime', 'datetime_isoformat', 'parse_duration',
|
||||
'duration_isoformat', 'ISO8601Error', 'parse_tzinfo',
|
||||
'tz_isoformat', 'UTC', 'FixedOffset', 'LOCAL', 'Duration',
|
||||
'strftime', 'DATE_BAS_COMPLETE', 'DATE_BAS_ORD_COMPLETE',
|
||||
'DATE_BAS_WEEK', 'DATE_BAS_WEEK_COMPLETE', 'DATE_CENTURY',
|
||||
'DATE_EXT_COMPLETE', 'DATE_EXT_ORD_COMPLETE', 'DATE_EXT_WEEK',
|
||||
'DATE_EXT_WEEK_COMPLETE', 'DATE_MONTH', 'DATE_YEAR',
|
||||
'TIME_BAS_COMPLETE', 'TIME_BAS_MINUTE', 'TIME_EXT_COMPLETE',
|
||||
'TIME_EXT_MINUTE', 'TIME_HOUR', 'TZ_BAS', 'TZ_EXT', 'TZ_HOUR',
|
||||
'DT_BAS_COMPLETE', 'DT_EXT_COMPLETE', 'DT_BAS_ORD_COMPLETE',
|
||||
'DT_EXT_ORD_COMPLETE', 'DT_BAS_WEEK_COMPLETE',
|
||||
'DT_EXT_WEEK_COMPLETE', 'D_DEFAULT', 'D_WEEK', 'D_ALT_EXT',
|
||||
'D_ALT_BAS', 'D_ALT_BAS_ORD', 'D_ALT_EXT_ORD']
|
|
@ -0,0 +1,324 @@
|
|||
##############################################################################
|
||||
# Copyright 2009, Gerhard Weis
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of the authors nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||
##############################################################################
|
||||
'''
|
||||
This module defines a Duration class.
|
||||
|
||||
The class Duration allows to define durations in years and months and can be
|
||||
used as limited replacement for timedelta objects.
|
||||
'''
|
||||
from datetime import date, datetime, timedelta
|
||||
from decimal import Decimal, ROUND_FLOOR
|
||||
|
||||
|
||||
def fquotmod(val, low, high):
|
||||
'''
|
||||
A divmod function with boundaries.
|
||||
|
||||
'''
|
||||
# assumes that all the maths is done with Decimals.
|
||||
# divmod for Decimal uses truncate instead of floor as builtin
|
||||
# divmod, so we have to do it manually here.
|
||||
a, b = val - low, high - low
|
||||
div = (a / b).to_integral(ROUND_FLOOR)
|
||||
mod = a - div * b
|
||||
# if we were not usig Decimal, it would look like this.
|
||||
# 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.
|
||||
'''
|
||||
if not isinstance(months, Decimal):
|
||||
months = Decimal(str(months))
|
||||
if not isinstance(years, Decimal):
|
||||
years = Decimal(str(years))
|
||||
self.months = months
|
||||
self.years = years
|
||||
self.tdelta = timedelta(days, seconds, microseconds, milliseconds,
|
||||
minutes, hours, weeks)
|
||||
|
||||
def __getstate__(self):
|
||||
return self.__dict__
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__.update(state)
|
||||
|
||||
def __getattr__(self, name):
|
||||
'''
|
||||
Provide direct access to attributes of included timedelta instance.
|
||||
'''
|
||||
return getattr(self.tdelta, name)
|
||||
|
||||
def __str__(self):
|
||||
'''
|
||||
Return a string representation of this duration similar to timedelta.
|
||||
'''
|
||||
params = []
|
||||
if self.years:
|
||||
params.append('%d years' % self.years)
|
||||
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 __hash__(self):
|
||||
'''
|
||||
Return a hash of this instance so that it can be used in, for
|
||||
example, dicts and sets.
|
||||
'''
|
||||
return hash((self.tdelta, self.months, self.years))
|
||||
|
||||
def __neg__(self):
|
||||
"""
|
||||
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)):
|
||||
if (not(float(self.years).is_integer() and
|
||||
float(self.months).is_integer())):
|
||||
raise ValueError('fractional years or months not supported'
|
||||
' for date calculations')
|
||||
newmonth = other.month + self.months
|
||||
carry, newmonth = fquotmod(newmonth, 1, 13)
|
||||
newyear = other.year + self.years + carry
|
||||
maxdays = max_days_in_month(newyear, newmonth)
|
||||
if other.day > maxdays:
|
||||
newday = maxdays
|
||||
else:
|
||||
newday = other.day
|
||||
newdt = other.replace(year=newyear, month=newmonth, day=newday)
|
||||
return 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)):
|
||||
if (not(float(self.years).is_integer() and
|
||||
float(self.months).is_integer())):
|
||||
raise ValueError('fractional years or months not supported'
|
||||
' for date calculations')
|
||||
newmonth = other.month + self.months
|
||||
carry, newmonth = fquotmod(newmonth, 1, 13)
|
||||
newyear = other.year + self.years + carry
|
||||
maxdays = max_days_in_month(newyear, newmonth)
|
||||
if other.day > maxdays:
|
||||
newday = maxdays
|
||||
else:
|
||||
newday = other.day
|
||||
newdt = other.replace(year=newyear, month=newmonth, day=newday)
|
||||
return newdt + self.tdelta
|
||||
raise TypeError('unsupported operand type(s) for +: %s and %s' %
|
||||
(other.__class__, self.__class__))
|
||||
|
||||
def __mul__(self, other):
|
||||
if isinstance(other, int):
|
||||
newduration = Duration(
|
||||
years=self.years * other,
|
||||
months=self.months * other)
|
||||
newduration.tdelta = self.tdelta * other
|
||||
return newduration
|
||||
raise TypeError('unsupported operand type(s) for +: %s and %s' %
|
||||
(self.__class__, other.__class__))
|
||||
|
||||
def __rmul__(self, other):
|
||||
|
||||
if isinstance(other, int):
|
||||
newduration = Duration(
|
||||
years=self.years * other,
|
||||
months=self.months * other)
|
||||
newduration.tdelta = self.tdelta * other
|
||||
return newduration
|
||||
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)):
|
||||
if (not(float(self.years).is_integer() and
|
||||
float(self.months).is_integer())):
|
||||
raise ValueError('fractional years or months not supported'
|
||||
' for date calculations')
|
||||
newmonth = other.month - self.months
|
||||
carry, newmonth = fquotmod(newmonth, 1, 13)
|
||||
newyear = other.year - self.years + carry
|
||||
maxdays = max_days_in_month(newyear, newmonth)
|
||||
if other.day > maxdays:
|
||||
newday = maxdays
|
||||
else:
|
||||
newday = other.day
|
||||
newdt = other.replace(year=newyear, month=newmonth, day=newday)
|
||||
return newdt - self.tdelta
|
||||
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 totimedelta(self, start=None, end=None):
|
||||
'''
|
||||
Convert this duration into a timedelta object.
|
||||
|
||||
This method requires a start datetime or end datetimem, but raises
|
||||
an exception if both are given.
|
||||
'''
|
||||
if start is None and end is None:
|
||||
raise ValueError("start or end required")
|
||||
if start is not None and end is not None:
|
||||
raise ValueError("only start or end allowed")
|
||||
if start is not None:
|
||||
return (start + self) - start
|
||||
return end - (end - self)
|
|
@ -0,0 +1,204 @@
|
|||
##############################################################################
|
||||
# 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 'month' not in groups: # weekdate or ordinal date
|
||||
ret = date(sign * int(groups['year']), 1, 1)
|
||||
if 'week' in groups:
|
||||
isotuple = ret.isocalendar()
|
||||
if 'day' in groups:
|
||||
days = int(groups['day'] or 1)
|
||||
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)
|
|
@ -0,0 +1,68 @@
|
|||
##############################################################################
|
||||
# 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.isoerror import ISO8601Error
|
||||
from isodate.isotime import parse_time
|
||||
|
||||
|
||||
def parse_datetime(datetimestring):
|
||||
'''
|
||||
Parses ISO 8601 date-times into datetime.datetime objects.
|
||||
|
||||
This function uses parse_date and parse_time to do the job, so it allows
|
||||
more combinations of date and time representations, than the actual
|
||||
ISO 8601:2004 standard allows.
|
||||
'''
|
||||
try:
|
||||
datestring, timestring = datetimestring.split('T')
|
||||
except ValueError:
|
||||
raise ISO8601Error("ISO 8601 time designator 'T' missing. Unable to"
|
||||
" parse datetime string %r" % datetimestring)
|
||||
tmpdate = parse_date(datestring)
|
||||
tmptime = parse_time(timestring)
|
||||
return datetime.combine(tmpdate, tmptime)
|
||||
|
||||
|
||||
def datetime_isoformat(tdt, format=DATE_EXT_COMPLETE + 'T' +
|
||||
TIME_EXT_COMPLETE + TZ_EXT):
|
||||
'''
|
||||
Format datetime strings.
|
||||
|
||||
This method is just a wrapper around isodate.isostrf.strftime and uses
|
||||
Extended-Complete as default format.
|
||||
'''
|
||||
return strftime(tdt, format)
|
|
@ -0,0 +1,149 @@
|
|||
##############################################################################
|
||||
# Copyright 2009, Gerhard Weis
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of the authors nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||
##############################################################################
|
||||
'''
|
||||
This module provides an ISO 8601:2004 duration parser.
|
||||
|
||||
It also provides a wrapper to strftime. This wrapper makes it easier to
|
||||
format timedelta or Duration instances as ISO conforming strings.
|
||||
'''
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
import re
|
||||
|
||||
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(?!\b)"
|
||||
r"(?P<years>[0-9]+([,.][0-9]+)?Y)?"
|
||||
r"(?P<months>[0-9]+([,.][0-9]+)?M)?"
|
||||
r"(?P<weeks>[0-9]+([,.][0-9]+)?W)?"
|
||||
r"(?P<days>[0-9]+([,.][0-9]+)?D)?"
|
||||
r"((?P<separator>T)(?P<hours>[0-9]+([,.][0-9]+)?H)?"
|
||||
r"(?P<minutes>[0-9]+([,.][0-9]+)?M)?"
|
||||
r"(?P<seconds>[0-9]+([,.][0-9]+)?S)?)?$")
|
||||
# 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]
|
||||
if key in ('years', 'months'):
|
||||
groups[key] = Decimal(groups[key][:-1].replace(',', '.'))
|
||||
else:
|
||||
# these values are passed into a timedelta object,
|
||||
# which works with floats.
|
||||
groups[key] = float(groups[key][:-1].replace(',', '.'))
|
||||
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
|
|
@ -0,0 +1,33 @@
|
|||
##############################################################################
|
||||
# Copyright 2009, Gerhard Weis
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of the authors nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||
##############################################################################
|
||||
'''
|
||||
This module defines all exception classes in the whole package.
|
||||
'''
|
||||
|
||||
|
||||
class ISO8601Error(ValueError):
|
||||
'''Raised when the given ISO string can not be parsed.'''
|
|
@ -0,0 +1,213 @@
|
|||
##############################################################################
|
||||
# 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: '%06d' % tdt.microsecond,
|
||||
'%H': lambda tdt, yds: '%02d' % tdt.hour,
|
||||
'%j': lambda tdt, yds: '%03d' % (tdt.toordinal() -
|
||||
date(tdt.year,
|
||||
1, 1).toordinal() +
|
||||
1),
|
||||
'%m': lambda tdt, yds: '%02d' % tdt.month,
|
||||
'%M': lambda tdt, yds: '%02d' % tdt.minute,
|
||||
'%S': lambda tdt, yds: '%02d' % tdt.second,
|
||||
'%w': lambda tdt, yds: '%1d' % tdt.isoweekday(),
|
||||
'%W': lambda tdt, yds: '%02d' % tdt.isocalendar()[1],
|
||||
'%Y': lambda tdt, yds: (((yds != 4) and '+') or '') +
|
||||
(('%%0%dd' % yds) % tdt.year),
|
||||
'%C': lambda tdt, yds: (((yds != 4) and '+') or '') +
|
||||
(('%%0%dd' % (yds - 2)) %
|
||||
(tdt.year / 100)),
|
||||
'%h': lambda tdt, yds: tz_isoformat(tdt, '%h'),
|
||||
'%Z': lambda tdt, yds: tz_isoformat(tdt, '%Z'),
|
||||
'%z': lambda tdt, yds: tz_isoformat(tdt, '%z'),
|
||||
'%%': lambda tdt, yds: '%'}
|
||||
|
||||
STRF_D_MAP = {'%d': lambda tdt, yds: '%02d' % tdt.days,
|
||||
'%f': lambda tdt, yds: '%06d' % tdt.microseconds,
|
||||
'%H': lambda tdt, yds: '%02d' % (tdt.seconds / 60 / 60),
|
||||
'%m': lambda tdt, yds: '%02d' % tdt.months,
|
||||
'%M': lambda tdt, yds: '%02d' % ((tdt.seconds / 60) % 60),
|
||||
'%S': lambda tdt, yds: '%02d' % (tdt.seconds % 60),
|
||||
'%W': lambda tdt, yds: '%02d' % (abs(tdt.days / 7)),
|
||||
'%Y': lambda tdt, yds: (((yds != 4) and '+') or '') +
|
||||
(('%%0%dd' % yds) % tdt.years),
|
||||
'%C': lambda tdt, yds: (((yds != 4) and '+') or '') +
|
||||
(('%%0%dd' % (yds - 2)) %
|
||||
(tdt.years / 100)),
|
||||
'%%': lambda tdt, yds: '%'}
|
||||
|
||||
|
||||
def _strfduration(tdt, format, yeardigits=4):
|
||||
'''
|
||||
this is the work method for timedelta and Duration instances.
|
||||
|
||||
see strftime for more details.
|
||||
'''
|
||||
def repl(match):
|
||||
'''
|
||||
lookup format command and return corresponding replacement.
|
||||
'''
|
||||
if match.group(0) in STRF_D_MAP:
|
||||
return STRF_D_MAP[match.group(0)](tdt, yeardigits)
|
||||
elif match.group(0) == '%P':
|
||||
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
|
||||
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)
|
|
@ -0,0 +1,158 @@
|
|||
##############################################################################
|
||||
# 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}"
|
||||
r"([,.][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}"
|
||||
r"([,.][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}"
|
||||
r"([,.][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}"
|
||||
r"([,.][0-9]+)?)" + TZ_REGEX))
|
||||
# hh.hh ... basic format
|
||||
TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2}"
|
||||
r"([,.][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:
|
||||
# round to microseconds if fractional seconds are more precise
|
||||
second = Decimal(groups['second']).quantize(Decimal('.000001'))
|
||||
microsecond = (second - int(second)) * long(1e6)
|
||||
# int(...) ... no rounding
|
||||
# to_integral() ... rounding
|
||||
return time(int(groups['hour']), int(groups['minute']),
|
||||
int(second), int(microsecond.to_integral()),
|
||||
tzinfo)
|
||||
if 'minute' in groups:
|
||||
minute = Decimal(groups['minute'])
|
||||
second = (minute - int(minute)) * 60
|
||||
microsecond = (second - int(second)) * long(1e6)
|
||||
return time(int(groups['hour']), int(minute), int(second),
|
||||
int(microsecond.to_integral()), tzinfo)
|
||||
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),
|
||||
int(microsecond.to_integral()), tzinfo)
|
||||
raise ISO8601Error('Unrecognised ISO 8601 time format: %r' % timestring)
|
||||
|
||||
|
||||
def time_isoformat(ttime, format=TIME_EXT_COMPLETE + TZ_EXT):
|
||||
'''
|
||||
Format time strings.
|
||||
|
||||
This method is just a wrapper around isodate.isostrf.strftime and uses
|
||||
Time-Extended-Complete with extended time zone as default format.
|
||||
'''
|
||||
return strftime(ttime, format)
|
|
@ -0,0 +1,112 @@
|
|||
##############################################################################
|
||||
# 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(dt, 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
|
||||
'''
|
||||
tzinfo = dt.tzinfo
|
||||
if (tzinfo is None) or (tzinfo.utcoffset(dt) is None):
|
||||
return ''
|
||||
if tzinfo.utcoffset(dt) == ZERO and tzinfo.dst(dt) == ZERO:
|
||||
return 'Z'
|
||||
tdelta = tzinfo.utcoffset(dt)
|
||||
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 ValueError('unknown format string "%s"' % format)
|
|
@ -0,0 +1,50 @@
|
|||
##############################################################################
|
||||
# Copyright 2009, Gerhard Weis
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of the authors nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||
##############################################################################
|
||||
'''
|
||||
Collect all test suites into one TestSuite instance.
|
||||
'''
|
||||
|
||||
import unittest
|
||||
from isodate.tests import (test_date, test_time, test_datetime, test_duration,
|
||||
test_strf, test_pickle)
|
||||
|
||||
|
||||
def test_suite():
|
||||
'''
|
||||
Return a new TestSuite instance consisting of all available TestSuites.
|
||||
'''
|
||||
return unittest.TestSuite([
|
||||
test_date.test_suite(),
|
||||
test_time.test_suite(),
|
||||
test_datetime.test_suite(),
|
||||
test_duration.test_suite(),
|
||||
test_strf.test_suite(),
|
||||
test_pickle.test_suite(),
|
||||
])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
|
@ -0,0 +1,129 @@
|
|||
##############################################################################
|
||||
# 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
|
||||
|
||||
|
||||
# load_tests Protocol
|
||||
def load_tests(loader, tests, pattern):
|
||||
return test_suite()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
|
@ -0,0 +1,146 @@
|
|||
##############################################################################
|
||||
# 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 ISO8601Error
|
||||
from isodate import DATE_BAS_COMPLETE, TIME_BAS_MINUTE, TIME_BAS_COMPLETE
|
||||
from isodate import DATE_EXT_COMPLETE, TIME_EXT_MINUTE, TIME_EXT_COMPLETE
|
||||
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,
|
||||
'19850412T1015'),
|
||||
('1985-04-12T10:15', datetime(1985, 4, 12, 10, 15),
|
||||
DATE_EXT_COMPLETE + 'T' + TIME_EXT_MINUTE,
|
||||
'1985-04-12T10:15'),
|
||||
('1985102T1015Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC),
|
||||
DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS,
|
||||
'1985102T1015Z'),
|
||||
('1985-102T10:15Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC),
|
||||
DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_EXT,
|
||||
'1985-102T10:15Z'),
|
||||
('1985W155T1015+0400', datetime(1985, 4, 12, 10, 15,
|
||||
tzinfo=FixedOffset(4, 0,
|
||||
'+0400')),
|
||||
DATE_BAS_WEEK_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS,
|
||||
'1985W155T1015+0400'),
|
||||
('1985-W15-5T10:15+04', datetime(1985, 4, 12, 10, 15,
|
||||
tzinfo=FixedOffset(4, 0,
|
||||
'+0400'),),
|
||||
DATE_EXT_WEEK_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_HOUR,
|
||||
'1985-W15-5T10:15+04'),
|
||||
('20110410T101225.123000Z',
|
||||
datetime(2011, 4, 10, 10, 12, 25, 123000, tzinfo=UTC),
|
||||
DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE + ".%f" + TZ_BAS,
|
||||
'20110410T101225.123000Z'),
|
||||
('2012-10-12T08:29:46.069178Z',
|
||||
datetime(2012, 10, 12, 8, 29, 46, 69178, tzinfo=UTC),
|
||||
DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
|
||||
'2012-10-12T08:29:46.069178Z'),
|
||||
('2012-10-12T08:29:46.691780Z',
|
||||
datetime(2012, 10, 12, 8, 29, 46, 691780, tzinfo=UTC),
|
||||
DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
|
||||
'2012-10-12T08:29:46.691780Z'),
|
||||
('2012-10-30T08:55:22.1234567Z',
|
||||
datetime(2012, 10, 30, 8, 55, 22, 123457, tzinfo=UTC),
|
||||
DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
|
||||
'2012-10-30T08:55:22.123457Z'),
|
||||
('2012-10-30T08:55:22.1234561Z',
|
||||
datetime(2012, 10, 30, 8, 55, 22, 123456, tzinfo=UTC),
|
||||
DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
|
||||
'2012-10-30T08:55:22.123456Z'),
|
||||
('2014-08-18 14:55:22.123456Z', None,
|
||||
DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
|
||||
'2014-08-18T14:55:22.123456Z'),
|
||||
]
|
||||
|
||||
|
||||
def create_testcase(datetimestring, expectation, format, output):
|
||||
"""
|
||||
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.
|
||||
'''
|
||||
if expectation is None:
|
||||
self.assertRaises(ISO8601Error, parse_datetime, datetimestring)
|
||||
else:
|
||||
self.assertEqual(parse_datetime(datetimestring), expectation)
|
||||
|
||||
def test_format(self):
|
||||
'''
|
||||
Take datetime object and create ISO string from it.
|
||||
This is the reverse test to test_parse.
|
||||
'''
|
||||
if expectation is None:
|
||||
self.assertRaises(AttributeError,
|
||||
datetime_isoformat, expectation, format)
|
||||
else:
|
||||
self.assertEqual(datetime_isoformat(expectation, format),
|
||||
output)
|
||||
|
||||
return unittest.TestLoader().loadTestsFromTestCase(TestDateTime)
|
||||
|
||||
|
||||
def test_suite():
|
||||
'''
|
||||
Construct a TestSuite instance for all test cases.
|
||||
'''
|
||||
suite = unittest.TestSuite()
|
||||
for datetimestring, expectation, format, output in TEST_CASES:
|
||||
suite.addTest(create_testcase(datetimestring, expectation,
|
||||
format, output))
|
||||
return suite
|
||||
|
||||
|
||||
# load_tests Protocol
|
||||
def load_tests(loader, tests, pattern):
|
||||
return test_suite()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
|
@ -0,0 +1,601 @@
|
|||
##############################################################################
|
||||
# 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)),
|
||||
(parse_duration("P1Y1M5W"),
|
||||
date(2000, 1, 30),
|
||||
date(2001, 4, 4)),
|
||||
(parse_duration("P0.5Y"),
|
||||
date(2000, 1, 30),
|
||||
None),
|
||||
(Duration(years=1, months=1, hours=3),
|
||||
datetime(2000, 1, 30, 12, 15, 00),
|
||||
datetime(2001, 2, 28, 15, 15, 00)),
|
||||
(parse_duration("P1Y1MT3H"),
|
||||
datetime(2000, 1, 30, 12, 15, 00),
|
||||
datetime(2001, 2, 28, 15, 15, 00)),
|
||||
(Duration(years=1, months=2),
|
||||
timedelta(days=1),
|
||||
Duration(years=1, months=2, days=1)),
|
||||
(timedelta(days=1),
|
||||
Duration(years=1, months=2),
|
||||
Duration(years=1, months=2, days=1)),
|
||||
(datetime(2008, 1, 1, 0, 2),
|
||||
Duration(months=1),
|
||||
datetime(2008, 2, 1, 0, 2)),
|
||||
(datetime.strptime("200802", "%Y%M"),
|
||||
parse_duration("P1M"),
|
||||
datetime(2008, 2, 1, 0, 2)),
|
||||
(datetime(2008, 2, 1),
|
||||
Duration(months=1),
|
||||
datetime(2008, 3, 1)),
|
||||
(datetime.strptime("200802", "%Y%m"),
|
||||
parse_duration("P1M"),
|
||||
datetime(2008, 3, 1)),
|
||||
# (date(2000, 1, 1),
|
||||
# Duration(years=1.5),
|
||||
# date(2001, 6, 1)),
|
||||
# (date(2000, 1, 1),
|
||||
# Duration(years=1, months=1.5),
|
||||
# date(2001, 2, 14)),
|
||||
)
|
||||
|
||||
# A list of test cases of multiplications of durations
|
||||
# are compared against a given expected result.
|
||||
DATE_MUL_TEST_CASES = (
|
||||
(Duration(years=1, months=1),
|
||||
3,
|
||||
Duration(years=3, months=3)),
|
||||
(Duration(years=1, months=1),
|
||||
-3,
|
||||
Duration(years=-3, months=-3)),
|
||||
(3,
|
||||
Duration(years=1, months=1),
|
||||
Duration(years=3, months=3)),
|
||||
(-3,
|
||||
Duration(years=1, months=1),
|
||||
Duration(years=-3, months=-3)),
|
||||
(5,
|
||||
Duration(years=2, minutes=40),
|
||||
Duration(years=10, hours=3, minutes=20)),
|
||||
(-5,
|
||||
Duration(years=2, minutes=40),
|
||||
Duration(years=-10, hours=-3, minutes=-20)),
|
||||
(7,
|
||||
Duration(years=1, months=2, weeks=40),
|
||||
Duration(years=8, months=2, weeks=280)))
|
||||
|
||||
|
||||
class DurationTest(unittest.TestCase):
|
||||
'''
|
||||
This class tests various other aspects of the isoduration module,
|
||||
which are not covered with the test cases listed above.
|
||||
'''
|
||||
|
||||
def test_associative(self):
|
||||
'''
|
||||
Adding 2 durations to a date is not associative.
|
||||
'''
|
||||
days1 = Duration(days=1)
|
||||
months1 = Duration(months=1)
|
||||
start = date(2000, 3, 30)
|
||||
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))
|
||||
self.assertRaises(TypeError, operator.add,
|
||||
Duration(years=1, months=1, weeks=5),
|
||||
'raise exception')
|
||||
self.assertRaises(TypeError, operator.add, 'raise exception',
|
||||
Duration(years=1, months=1, weeks=5))
|
||||
self.assertRaises(TypeError, operator.mul,
|
||||
Duration(years=1, months=1, weeks=5),
|
||||
'raise exception')
|
||||
self.assertRaises(TypeError, operator.mul, 'raise exception',
|
||||
Duration(years=1, months=1, weeks=5))
|
||||
self.assertRaises(TypeError, operator.mul,
|
||||
Duration(years=1, months=1, weeks=5),
|
||||
3.14)
|
||||
self.assertRaises(TypeError, operator.mul, 3.14,
|
||||
Duration(years=1, months=1, weeks=5))
|
||||
|
||||
def test_parseerror(self):
|
||||
'''
|
||||
Test for unparseable duration string.
|
||||
'''
|
||||
self.assertRaises(ISO8601Error, parse_duration, 'T10:10:10')
|
||||
|
||||
def test_repr(self):
|
||||
'''
|
||||
Test __repr__ and __str__ for Duration objects.
|
||||
'''
|
||||
dur = Duration(10, 10, years=10, months=10)
|
||||
self.assertEqual('10 years, 10 months, 10 days, 0:00:10', str(dur))
|
||||
self.assertEqual('isodate.duration.Duration(10, 10, 0,'
|
||||
' years=10, months=10)', repr(dur))
|
||||
|
||||
def test_hash(self):
|
||||
'''
|
||||
Test __hash__ for Duration objects.
|
||||
'''
|
||||
dur1 = Duration(10, 10, years=10, months=10)
|
||||
dur2 = Duration(9, 9, years=9, months=9)
|
||||
dur3 = Duration(10, 10, years=10, months=10)
|
||||
self.assertNotEqual(hash(dur1), hash(dur2))
|
||||
self.assertNotEqual(id(dur1), id(dur2))
|
||||
self.assertEqual(hash(dur1), hash(dur3))
|
||||
self.assertNotEqual(id(dur1), id(dur3))
|
||||
durSet = set()
|
||||
durSet.add(dur1)
|
||||
durSet.add(dur2)
|
||||
durSet.add(dur3)
|
||||
self.assertEqual(len(durSet), 2)
|
||||
|
||||
def test_neg(self):
|
||||
'''
|
||||
Test __neg__ for Duration objects.
|
||||
'''
|
||||
self.assertEqual(-Duration(0), Duration(0))
|
||||
self.assertEqual(-Duration(years=1, months=1),
|
||||
Duration(years=-1, months=-1))
|
||||
self.assertEqual(-Duration(years=1, months=1), Duration(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 test_totimedelta(self):
|
||||
'''
|
||||
Test conversion form Duration to timedelta.
|
||||
'''
|
||||
dur = Duration(years=1, months=2, days=10)
|
||||
self.assertEqual(dur.totimedelta(datetime(1998, 2, 25)),
|
||||
timedelta(434))
|
||||
# leap year has one day more in february
|
||||
self.assertEqual(dur.totimedelta(datetime(2000, 2, 25)),
|
||||
timedelta(435))
|
||||
dur = Duration(months=2)
|
||||
# march is longer than february, but april is shorter than
|
||||
# march (cause only one day difference compared to 2)
|
||||
self.assertEqual(dur.totimedelta(datetime(2000, 2, 25)), timedelta(60))
|
||||
self.assertEqual(dur.totimedelta(datetime(2001, 2, 25)), timedelta(59))
|
||||
self.assertEqual(dur.totimedelta(datetime(2001, 3, 25)), timedelta(61))
|
||||
|
||||
|
||||
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(ValueError, operator.add, start, duration)
|
||||
else:
|
||||
self.assertEqual(start + duration, expectation)
|
||||
|
||||
return unittest.TestLoader().loadTestsFromTestCase(TestDateCalc)
|
||||
|
||||
|
||||
def create_datemultestcase(operand1, operand2, expectation):
|
||||
"""
|
||||
Create a TestCase class for a specific test.
|
||||
|
||||
This allows having a separate TestCase for each test tuple from the
|
||||
DATE_CALC_TEST_CASES list, so that a failed test won't stop other tests.
|
||||
"""
|
||||
|
||||
class TestDateMul(unittest.TestCase):
|
||||
'''
|
||||
A test case template test addition operators for Duration objects.
|
||||
'''
|
||||
|
||||
def test_mul(self):
|
||||
'''
|
||||
Test operator *.
|
||||
'''
|
||||
self.assertEqual(operand1 * operand2, expectation)
|
||||
|
||||
return unittest.TestLoader().loadTestsFromTestCase(TestDateMul)
|
||||
|
||||
|
||||
def test_suite():
|
||||
'''
|
||||
Return a test suite containing all test defined above.
|
||||
'''
|
||||
suite = unittest.TestSuite()
|
||||
for durationstring, (expectation, format,
|
||||
altstr) in PARSE_TEST_CASES.items():
|
||||
suite.addTest(create_parsetestcase(durationstring, expectation,
|
||||
format, altstr))
|
||||
for testdata in MATH_TEST_CASES:
|
||||
suite.addTest(create_mathtestcase(*testdata))
|
||||
for testdata in DATE_TEST_CASES:
|
||||
suite.addTest(create_datetestcase(*testdata))
|
||||
for testdata in DATE_CALC_TEST_CASES:
|
||||
suite.addTest(create_datecalctestcase(*testdata))
|
||||
for testdata in DATE_MUL_TEST_CASES:
|
||||
suite.addTest(create_datemultestcase(*testdata))
|
||||
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(DurationTest))
|
||||
return suite
|
||||
|
||||
|
||||
# load_tests Protocol
|
||||
def load_tests(loader, tests, pattern):
|
||||
return test_suite()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
|
@ -0,0 +1,54 @@
|
|||
import unittest
|
||||
import cPickle as pickle
|
||||
import isodate
|
||||
|
||||
|
||||
class TestPickle(unittest.TestCase):
|
||||
'''
|
||||
A test case template to parse an ISO datetime string into a
|
||||
datetime object.
|
||||
'''
|
||||
|
||||
def test_pickle_datetime(self):
|
||||
'''
|
||||
Parse an ISO datetime string and compare it to the expected value.
|
||||
'''
|
||||
dti = isodate.parse_datetime('2012-10-26T09:33+00:00')
|
||||
for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
|
||||
pikl = pickle.dumps(dti, proto)
|
||||
self.assertEqual(dti, pickle.loads(pikl),
|
||||
"pickle proto %d failed" % proto)
|
||||
|
||||
def test_pickle_duration(self):
|
||||
'''
|
||||
Pickle / unpickle duration objects.
|
||||
'''
|
||||
from isodate.duration import Duration
|
||||
dur = Duration()
|
||||
failed = []
|
||||
for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
|
||||
try:
|
||||
pikl = pickle.dumps(dur, proto)
|
||||
if dur != pickle.loads(pikl):
|
||||
raise Exception("not equal")
|
||||
except Exception, e:
|
||||
failed.append("pickle proto %d failed (%s)" % (proto, repr(e)))
|
||||
self.assertEqual(len(failed), 0, "pickle protos failed: %s" %
|
||||
str(failed))
|
||||
|
||||
|
||||
def test_suite():
|
||||
'''
|
||||
Construct a TestSuite instance for all test cases.
|
||||
'''
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestPickle))
|
||||
return suite
|
||||
|
||||
|
||||
# load_tests Protocol
|
||||
def load_tests(loader, tests, pattern):
|
||||
return test_suite()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
|
@ -0,0 +1,135 @@
|
|||
##############################################################################
|
||||
# Copyright 2009, Gerhard Weis
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of the authors nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT
|
||||
##############################################################################
|
||||
'''
|
||||
Test cases for the isodate module.
|
||||
'''
|
||||
import unittest
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from isodate import strftime
|
||||
from isodate import LOCAL
|
||||
from isodate import DT_EXT_COMPLETE
|
||||
from isodate import tzinfo
|
||||
|
||||
|
||||
TEST_CASES = ((datetime(2012, 12, 25, 13, 30, 0, 0, LOCAL), DT_EXT_COMPLETE,
|
||||
"2012-12-25T13:30:00+10:00"),
|
||||
# DST ON
|
||||
(datetime(1999, 12, 25, 13, 30, 0, 0, LOCAL), DT_EXT_COMPLETE,
|
||||
"1999-12-25T13:30:00+11:00"),
|
||||
# microseconds
|
||||
(datetime(2012, 10, 12, 8, 29, 46, 69178),
|
||||
"%Y-%m-%dT%H:%M:%S.%f",
|
||||
"2012-10-12T08:29:46.069178"),
|
||||
(datetime(2012, 10, 12, 8, 29, 46, 691780),
|
||||
"%Y-%m-%dT%H:%M:%S.%f",
|
||||
"2012-10-12T08:29:46.691780"),
|
||||
)
|
||||
|
||||
|
||||
def create_testcase(dt, format, expectation):
|
||||
"""
|
||||
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 test ISO date formatting.
|
||||
'''
|
||||
|
||||
# local time zone mock function
|
||||
def localtime_mock(self, secs):
|
||||
"""
|
||||
mock time.localtime so that it always returns a time_struct with
|
||||
tm_idst=1
|
||||
"""
|
||||
tt = self.ORIG['localtime'](secs)
|
||||
# befor 2000 everything is dst, after 2000 no dst.
|
||||
if tt.tm_year < 2000:
|
||||
dst = 1
|
||||
else:
|
||||
dst = 0
|
||||
tt = (tt.tm_year, tt.tm_mon, tt.tm_mday,
|
||||
tt.tm_hour, tt.tm_min, tt.tm_sec,
|
||||
tt.tm_wday, tt.tm_yday, dst)
|
||||
return time.struct_time(tt)
|
||||
|
||||
def setUp(self):
|
||||
self.ORIG = {}
|
||||
self.ORIG['STDOFFSET'] = tzinfo.STDOFFSET
|
||||
self.ORIG['DSTOFFSET'] = tzinfo.DSTOFFSET
|
||||
self.ORIG['DSTDIFF'] = tzinfo.DSTDIFF
|
||||
self.ORIG['localtime'] = time.localtime
|
||||
# ovveride all saved values with fixtures.
|
||||
# calculate LOCAL TZ offset, so that this test runs in
|
||||
# every time zone
|
||||
tzinfo.STDOFFSET = timedelta(seconds=36000) # assume LOC = +10:00
|
||||
tzinfo.DSTOFFSET = timedelta(seconds=39600) # assume DST = +11:00
|
||||
tzinfo.DSTDIFF = tzinfo.DSTOFFSET - tzinfo.STDOFFSET
|
||||
time.localtime = self.localtime_mock
|
||||
|
||||
def tearDown(self):
|
||||
# restore test fixtures
|
||||
tzinfo.STDOFFSET = self.ORIG['STDOFFSET']
|
||||
tzinfo.DSTOFFSET = self.ORIG['DSTOFFSET']
|
||||
tzinfo.DSTDIFF = self.ORIG['DSTDIFF']
|
||||
time.localtime = self.ORIG['localtime']
|
||||
|
||||
def test_format(self):
|
||||
'''
|
||||
Take date object and create ISO string from it.
|
||||
This is the reverse test to test_parse.
|
||||
'''
|
||||
if expectation is None:
|
||||
self.assertRaises(AttributeError,
|
||||
strftime(dt, format))
|
||||
else:
|
||||
self.assertEqual(strftime(dt, format),
|
||||
expectation)
|
||||
|
||||
return unittest.TestLoader().loadTestsFromTestCase(TestDate)
|
||||
|
||||
|
||||
def test_suite():
|
||||
'''
|
||||
Construct a TestSuite instance for all test cases.
|
||||
'''
|
||||
suite = unittest.TestSuite()
|
||||
for dt, format, expectation in TEST_CASES:
|
||||
suite.addTest(create_testcase(dt, format, expectation))
|
||||
return suite
|
||||
|
||||
|
||||
# load_tests Protocol
|
||||
def load_tests(loader, tests, pattern):
|
||||
return test_suite()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
|
@ -0,0 +1,143 @@
|
|||
##############################################################################
|
||||
# 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
|
||||
|
||||
|
||||
# load_tests Protocol
|
||||
def load_tests(loader, tests, pattern):
|
||||
return test_suite()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
|
@ -0,0 +1,142 @@
|
|||
'''
|
||||
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, 0, "UTC") or FixedOffset() is a different way to
|
||||
build a UTC tzinfo object.
|
||||
'''
|
||||
|
||||
def __init__(self, offset_hours=0, offset_minutes=0, name="UTC"):
|
||||
'''
|
||||
Initialise an instance with time offset and name.
|
||||
The time offset should be positive for time zones east of UTC
|
||||
and negate for time zones west of UTC.
|
||||
'''
|
||||
self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes)
|
||||
self.__name = name
|
||||
|
||||
def utcoffset(self, dt):
|
||||
'''
|
||||
Return offset from UTC in minutes of UTC.
|
||||
'''
|
||||
return self.__offset
|
||||
|
||||
def tzname(self, dt):
|
||||
'''
|
||||
Return the time zone name corresponding to the datetime object dt, as a
|
||||
string.
|
||||
'''
|
||||
return self.__name
|
||||
|
||||
def dst(self, dt):
|
||||
'''
|
||||
Return the daylight saving time (DST) adjustment, in minutes east of
|
||||
UTC.
|
||||
'''
|
||||
return ZERO
|
||||
|
||||
def __repr__(self):
|
||||
'''
|
||||
Return nicely formatted repr string.
|
||||
'''
|
||||
return "<FixedOffset %r>" % self.__name
|
||||
|
||||
|
||||
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.
|
Loading…
Reference in New Issue