Import upstream version 0.6.1+git20211213.1.d0eb6b9
Debian Janitor
2 years ago
1 | 1 | CHANGES |
2 | 2 | ======= |
3 | 3 | |
4 | 0.7.0 (unreleased) | |
5 | ------------------ | |
6 | ||
7 | - drop end of life python versions | |
8 | - Don't match garbage characters at the end of parsed strings #16 (Gabriel de Perthuis) | |
9 | - Breaking: fractional seconds are cut off to microseconds (round down) | |
10 | - Allow control over return type of parse_duration #64 (Felix Claessen) | |
11 | ||
12 | ||
4 | 13 | 0.6.1 (2021-12-13) |
5 | 14 | ------------------ |
6 | 15 | |
7 | - support python 3.10 () | |
16 | - support python 3.10 (Hugo van Kemenade) | |
8 | 17 | - last version to support py 2.7 |
9 | 18 | |
10 | 19 |
0 | Copyright (c) 2021, Hugo van Kemenade and contributors | |
1 | Copyright (c) 2009-2018, Gerhard Weis and contributors | |
2 | Copyright (c) 2009, Gerhard Weis | |
3 | All rights reserved. | |
4 | ||
5 | Redistribution and use in source and binary forms, with or without | |
6 | modification, are permitted provided that the following conditions are met: | |
7 | * Redistributions of source code must retain the above copyright | |
8 | notice, this list of conditions and the following disclaimer. | |
9 | * Redistributions in binary form must reproduce the above copyright | |
10 | notice, this list of conditions and the following disclaimer in the | |
11 | documentation and/or other materials provided with the distribution. | |
12 | * Neither the name of the <organization> nor the | |
13 | names of its contributors may be used to endorse or promote products | |
14 | derived from this software without specific prior written permission. | |
15 | ||
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
19 | DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY | |
20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
0 | 0 | Metadata-Version: 2.1 |
1 | 1 | Name: isodate |
2 | Version: 0.6.1 | |
2 | Version: 0.7.0.dev0 | |
3 | 3 | Summary: An ISO 8601 date/time/duration parser and formatter |
4 | 4 | Home-page: https://github.com/gweis/isodate/ |
5 | 5 | Author: Gerhard Weis |
6 | 6 | Author-email: gerhard.weis@proclos.com |
7 | License: BSD | |
7 | License: BSD-3-Clause | |
8 | 8 | Platform: UNKNOWN |
9 | 9 | Classifier: Development Status :: 4 - Beta |
10 | 10 | Classifier: Intended Audience :: Developers |
11 | 11 | Classifier: License :: OSI Approved :: BSD License |
12 | 12 | Classifier: Operating System :: OS Independent |
13 | 13 | Classifier: Programming Language :: Python |
14 | Classifier: Programming Language :: Python :: 2 | |
15 | Classifier: Programming Language :: Python :: 2.7 | |
16 | 14 | Classifier: Programming Language :: Python :: 3 |
17 | Classifier: Programming Language :: Python :: 3.6 | |
18 | 15 | Classifier: Programming Language :: Python :: 3.7 |
19 | 16 | Classifier: Programming Language :: Python :: 3.8 |
20 | 17 | Classifier: Programming Language :: Python :: 3.9 |
22 | 19 | Classifier: Programming Language :: Python :: Implementation :: PyPy |
23 | 20 | Classifier: Topic :: Internet |
24 | 21 | Classifier: Topic :: Software Development :: Libraries :: Python Modules |
22 | License-File: LICENSE | |
25 | 23 | |
26 | 24 | |
27 | 25 | ISO 8601 date/time parser |
63 | 61 | Documentation |
64 | 62 | ------------- |
65 | 63 | |
66 | Currently there are four parsing methods available. | |
64 | The following parsing methods are available. | |
67 | 65 | * parse_time: |
68 | 66 | parses an ISO 8601 time string into a *time* object |
69 | 67 | * parse_date: |
108 | 106 | prior 1900. This method also understands how to format *datetime* and |
109 | 107 | *Duration* instances. |
110 | 108 | |
111 | Installation: | |
112 | ------------- | |
109 | Installation | |
110 | ------------ | |
113 | 111 | |
114 | 112 | This module can easily be installed with Python standard installation methods. |
115 | 113 | |
116 | 114 | Either use *python setup.py install* or in case you have *setuptools* or |
117 | 115 | *distribute* available, you can also use *easy_install*. |
118 | 116 | |
119 | Limitations: | |
120 | ------------ | |
117 | Limitations | |
118 | ----------- | |
121 | 119 | |
122 | 120 | * The parser accepts several date/time representation which should be invalid |
123 | 121 | according to ISO 8601 standard. |
129 | 127 | 1901-01-01. |
130 | 128 | 3. negative *Duration* and *timedelta* value are not fully supported yet. |
131 | 129 | |
132 | Further information: | |
133 | -------------------- | |
130 | Further information | |
131 | ------------------- | |
134 | 132 | |
135 | 133 | The doc strings and unit tests should provide rather detailed information about |
136 | 134 | the methods and their limitations. |
143 | 141 | CHANGES |
144 | 142 | ======= |
145 | 143 | |
144 | 0.7.0 (unreleased) | |
145 | ------------------ | |
146 | ||
147 | - drop end of life python versions | |
148 | - Don't match garbage characters at the end of parsed strings #16 (Gabriel de Perthuis) | |
149 | - Breaking: fractional seconds are cut off to microseconds (round down) | |
150 | - Allow control over return type of parse_duration #64 (Felix Claessen) | |
151 | ||
152 | ||
146 | 153 | 0.6.1 (2021-12-13) |
147 | 154 | ------------------ |
148 | 155 | |
149 | - support python 3.10 () | |
156 | - support python 3.10 (Hugo van Kemenade) | |
150 | 157 | - last version to support py 2.7 |
151 | 158 | |
152 | 159 |
37 | 37 | Documentation |
38 | 38 | ------------- |
39 | 39 | |
40 | Currently there are four parsing methods available. | |
40 | The following parsing methods are available. | |
41 | 41 | * parse_time: |
42 | 42 | parses an ISO 8601 time string into a *time* object |
43 | 43 | * parse_date: |
82 | 82 | prior 1900. This method also understands how to format *datetime* and |
83 | 83 | *Duration* instances. |
84 | 84 | |
85 | Installation: | |
86 | ------------- | |
85 | Installation | |
86 | ------------ | |
87 | 87 | |
88 | 88 | This module can easily be installed with Python standard installation methods. |
89 | 89 | |
90 | 90 | Either use *python setup.py install* or in case you have *setuptools* or |
91 | 91 | *distribute* available, you can also use *easy_install*. |
92 | 92 | |
93 | Limitations: | |
94 | ------------ | |
93 | Limitations | |
94 | ----------- | |
95 | 95 | |
96 | 96 | * The parser accepts several date/time representation which should be invalid |
97 | 97 | according to ISO 8601 standard. |
103 | 103 | 1901-01-01. |
104 | 104 | 3. negative *Duration* and *timedelta* value are not fully supported yet. |
105 | 105 | |
106 | Further information: | |
107 | -------------------- | |
106 | Further information | |
107 | ------------------- | |
108 | 108 | |
109 | 109 | The doc strings and unit tests should provide rather detailed information about |
110 | 110 | the methods and their limitations. |
0 | 0 | #!/usr/bin/env python |
1 | ############################################################################## | |
2 | # Copyright 2009, Gerhard Weis | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions are met: | |
7 | # | |
8 | # * Redistributions of source code must retain the above copyright notice, | |
9 | # this list of conditions and the following disclaimer. | |
10 | # * Redistributions in binary form must reproduce the above copyright notice, | |
11 | # this list of conditions and the following disclaimer in the documentation | |
12 | # and/or other materials provided with the distribution. | |
13 | # * Neither the name of the authors nor the names of its contributors | |
14 | # may be used to endorse or promote products derived from this software | |
15 | # without specific prior written permission. | |
16 | # | |
17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
18 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
19 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
20 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
21 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
22 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
23 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
24 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
25 | # CONTRACT, STRICT LIABILITY, OR TORT | |
26 | ############################################################################## | |
27 | 1 | import os |
28 | 2 | from setuptools import setup |
29 | 3 | |
30 | 4 | |
31 | 5 | def read(*rnames): |
32 | return open(os.path.join(os.path.dirname(__file__), *rnames)).read() | |
6 | with open(os.path.join(os.path.dirname(__file__), *rnames)) as read_file: | |
7 | return read_file.read() | |
33 | 8 | |
34 | 9 | |
35 | setup(name='isodate', | |
36 | version='0.6.1', | |
37 | packages=['isodate', 'isodate.tests'], | |
38 | package_dir={'': 'src'}, | |
39 | ||
40 | # dependencies: | |
41 | install_requires=[ | |
42 | 'six' | |
43 | ], | |
44 | ||
45 | # PyPI metadata | |
46 | author='Gerhard Weis', | |
47 | author_email='gerhard.weis@proclos.com', | |
48 | description='An ISO 8601 date/time/duration parser and formatter', | |
49 | license='BSD', | |
50 | # keywords = '', | |
51 | url='https://github.com/gweis/isodate/', | |
52 | ||
53 | long_description=(read('README.rst') + | |
54 | read('CHANGES.txt') + | |
55 | read('TODO.txt')), | |
56 | ||
57 | classifiers=['Development Status :: 4 - Beta', | |
58 | # 'Environment :: Web Environment', | |
59 | 'Intended Audience :: Developers', | |
60 | 'License :: OSI Approved :: BSD License', | |
61 | 'Operating System :: OS Independent', | |
62 | 'Programming Language :: Python', | |
63 | 'Programming Language :: Python :: 2', | |
64 | 'Programming Language :: Python :: 2.7', | |
65 | 'Programming Language :: Python :: 3', | |
66 | 'Programming Language :: Python :: 3.6', | |
67 | 'Programming Language :: Python :: 3.7', | |
68 | 'Programming Language :: Python :: 3.8', | |
69 | 'Programming Language :: Python :: 3.9', | |
70 | 'Programming Language :: Python :: 3.10', | |
71 | 'Programming Language :: Python :: Implementation :: PyPy', | |
72 | 'Topic :: Internet', | |
73 | ('Topic :: Software Development :' | |
74 | ': Libraries :: Python Modules'), | |
75 | ], | |
76 | test_suite='isodate.tests.test_suite') | |
10 | setup( | |
11 | name="isodate", | |
12 | version="0.7.0.dev0", | |
13 | packages=["isodate", "isodate.tests"], | |
14 | package_dir={"": "src"}, | |
15 | # PyPI metadata | |
16 | author="Gerhard Weis", | |
17 | author_email="gerhard.weis@proclos.com", | |
18 | description="An ISO 8601 date/time/duration parser and formatter", | |
19 | license="BSD-3-Clause", | |
20 | license_files=("LICENSE",), | |
21 | # keywords = '', | |
22 | url="https://github.com/gweis/isodate/", | |
23 | long_description=(read("README.rst") + read("CHANGES.txt") + read("TODO.txt")), | |
24 | classifiers=[ | |
25 | "Development Status :: 4 - Beta", | |
26 | # 'Environment :: Web Environment', | |
27 | "Intended Audience :: Developers", | |
28 | "License :: OSI Approved :: BSD License", | |
29 | "Operating System :: OS Independent", | |
30 | "Programming Language :: Python", | |
31 | "Programming Language :: Python :: 3", | |
32 | "Programming Language :: Python :: 3.7", | |
33 | "Programming Language :: Python :: 3.8", | |
34 | "Programming Language :: Python :: 3.9", | |
35 | "Programming Language :: Python :: 3.10", | |
36 | "Programming Language :: Python :: Implementation :: PyPy", | |
37 | "Topic :: Internet", | |
38 | ("Topic :: Software Development :" ": Libraries :: Python Modules"), | |
39 | ], | |
40 | test_suite="isodate.tests.test_suite", | |
41 | ) |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | Import all essential functions and constants to re-export them here for easy |
28 | 2 | access. |
29 | 3 | |
30 | 4 | This module contains also various pre-defined ISO 8601 format strings. |
31 | ''' | |
5 | """ | |
32 | 6 | from isodate.isodates import parse_date, date_isoformat |
33 | 7 | from isodate.isotime import parse_time, time_isoformat |
34 | 8 | from isodate.isodatetime import parse_datetime, datetime_isoformat |
54 | 28 | from isodate.isostrf import D_DEFAULT, D_WEEK, D_ALT_EXT, D_ALT_BAS |
55 | 29 | from isodate.isostrf import D_ALT_BAS_ORD, D_ALT_EXT_ORD |
56 | 30 | |
57 | __all__ = ['parse_date', 'date_isoformat', 'parse_time', 'time_isoformat', | |
58 | 'parse_datetime', 'datetime_isoformat', 'parse_duration', | |
59 | 'duration_isoformat', 'ISO8601Error', 'parse_tzinfo', | |
60 | 'tz_isoformat', 'UTC', 'FixedOffset', 'LOCAL', 'Duration', | |
61 | 'strftime', 'DATE_BAS_COMPLETE', 'DATE_BAS_ORD_COMPLETE', | |
62 | 'DATE_BAS_WEEK', 'DATE_BAS_WEEK_COMPLETE', 'DATE_CENTURY', | |
63 | 'DATE_EXT_COMPLETE', 'DATE_EXT_ORD_COMPLETE', 'DATE_EXT_WEEK', | |
64 | 'DATE_EXT_WEEK_COMPLETE', 'DATE_YEAR', | |
65 | 'DATE_BAS_MONTH', 'DATE_EXT_MONTH', | |
66 | 'TIME_BAS_COMPLETE', 'TIME_BAS_MINUTE', 'TIME_EXT_COMPLETE', | |
67 | 'TIME_EXT_MINUTE', 'TIME_HOUR', 'TZ_BAS', 'TZ_EXT', 'TZ_HOUR', | |
68 | 'DT_BAS_COMPLETE', 'DT_EXT_COMPLETE', 'DT_BAS_ORD_COMPLETE', | |
69 | 'DT_EXT_ORD_COMPLETE', 'DT_BAS_WEEK_COMPLETE', | |
70 | 'DT_EXT_WEEK_COMPLETE', 'D_DEFAULT', 'D_WEEK', 'D_ALT_EXT', | |
71 | 'D_ALT_BAS', 'D_ALT_BAS_ORD', 'D_ALT_EXT_ORD'] | |
31 | __all__ = [ | |
32 | "parse_date", | |
33 | "date_isoformat", | |
34 | "parse_time", | |
35 | "time_isoformat", | |
36 | "parse_datetime", | |
37 | "datetime_isoformat", | |
38 | "parse_duration", | |
39 | "duration_isoformat", | |
40 | "ISO8601Error", | |
41 | "parse_tzinfo", | |
42 | "tz_isoformat", | |
43 | "UTC", | |
44 | "FixedOffset", | |
45 | "LOCAL", | |
46 | "Duration", | |
47 | "strftime", | |
48 | "DATE_BAS_COMPLETE", | |
49 | "DATE_BAS_ORD_COMPLETE", | |
50 | "DATE_BAS_WEEK", | |
51 | "DATE_BAS_WEEK_COMPLETE", | |
52 | "DATE_CENTURY", | |
53 | "DATE_EXT_COMPLETE", | |
54 | "DATE_EXT_ORD_COMPLETE", | |
55 | "DATE_EXT_WEEK", | |
56 | "DATE_EXT_WEEK_COMPLETE", | |
57 | "DATE_YEAR", | |
58 | "DATE_BAS_MONTH", | |
59 | "DATE_EXT_MONTH", | |
60 | "TIME_BAS_COMPLETE", | |
61 | "TIME_BAS_MINUTE", | |
62 | "TIME_EXT_COMPLETE", | |
63 | "TIME_EXT_MINUTE", | |
64 | "TIME_HOUR", | |
65 | "TZ_BAS", | |
66 | "TZ_EXT", | |
67 | "TZ_HOUR", | |
68 | "DT_BAS_COMPLETE", | |
69 | "DT_EXT_COMPLETE", | |
70 | "DT_BAS_ORD_COMPLETE", | |
71 | "DT_EXT_ORD_COMPLETE", | |
72 | "DT_BAS_WEEK_COMPLETE", | |
73 | "DT_EXT_WEEK_COMPLETE", | |
74 | "D_DEFAULT", | |
75 | "D_WEEK", | |
76 | "D_ALT_EXT", | |
77 | "D_ALT_BAS", | |
78 | "D_ALT_BAS_ORD", | |
79 | "D_ALT_EXT_ORD", | |
80 | ] |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | This module defines a Duration class. |
28 | 2 | |
29 | 3 | The class Duration allows to define durations in years and months and can be |
30 | 4 | used as limited replacement for timedelta objects. |
31 | ''' | |
5 | """ | |
32 | 6 | from datetime import timedelta |
33 | 7 | from decimal import Decimal, ROUND_FLOOR |
34 | 8 | |
35 | 9 | |
36 | 10 | def fquotmod(val, low, high): |
37 | ''' | |
11 | """ | |
38 | 12 | A divmod function with boundaries. |
39 | 13 | |
40 | ''' | |
14 | """ | |
41 | 15 | # assumes that all the maths is done with Decimals. |
42 | 16 | # divmod for Decimal uses truncate instead of floor as builtin |
43 | 17 | # divmod, so we have to do it manually here. |
51 | 25 | |
52 | 26 | |
53 | 27 | def max_days_in_month(year, month): |
54 | ''' | |
28 | """ | |
55 | 29 | Determines the number of days of a specific month in a specific year. |
56 | ''' | |
30 | """ | |
57 | 31 | if month in (1, 3, 5, 7, 8, 10, 12): |
58 | 32 | return 31 |
59 | 33 | if month in (4, 6, 9, 11): |
64 | 38 | |
65 | 39 | |
66 | 40 | class Duration(object): |
67 | ''' | |
41 | """ | |
68 | 42 | A class which represents a duration. |
69 | 43 | |
70 | 44 | The difference to datetime.timedelta is, that this class handles also |
84 | 58 | |
85 | 59 | The algorithm to add a duration to a date is defined at |
86 | 60 | http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes |
87 | ''' | |
88 | ||
89 | def __init__(self, days=0, seconds=0, microseconds=0, milliseconds=0, | |
90 | minutes=0, hours=0, weeks=0, months=0, years=0): | |
91 | ''' | |
61 | """ | |
62 | ||
63 | def __init__( | |
64 | self, | |
65 | days=0, | |
66 | seconds=0, | |
67 | microseconds=0, | |
68 | milliseconds=0, | |
69 | minutes=0, | |
70 | hours=0, | |
71 | weeks=0, | |
72 | months=0, | |
73 | years=0, | |
74 | ): | |
75 | """ | |
92 | 76 | Initialise this Duration instance with the given parameters. |
93 | ''' | |
77 | """ | |
94 | 78 | if not isinstance(months, Decimal): |
95 | 79 | months = Decimal(str(months)) |
96 | 80 | if not isinstance(years, Decimal): |
97 | 81 | years = Decimal(str(years)) |
98 | 82 | self.months = months |
99 | 83 | self.years = years |
100 | self.tdelta = timedelta(days, seconds, microseconds, milliseconds, | |
101 | minutes, hours, weeks) | |
84 | self.tdelta = timedelta( | |
85 | days, seconds, microseconds, milliseconds, minutes, hours, weeks | |
86 | ) | |
102 | 87 | |
103 | 88 | def __getstate__(self): |
104 | 89 | return self.__dict__ |
107 | 92 | self.__dict__.update(state) |
108 | 93 | |
109 | 94 | def __getattr__(self, name): |
110 | ''' | |
95 | """ | |
111 | 96 | Provide direct access to attributes of included timedelta instance. |
112 | ''' | |
97 | """ | |
113 | 98 | return getattr(self.tdelta, name) |
114 | 99 | |
115 | 100 | def __str__(self): |
116 | ''' | |
101 | """ | |
117 | 102 | Return a string representation of this duration similar to timedelta. |
118 | ''' | |
103 | """ | |
119 | 104 | params = [] |
120 | 105 | if self.years: |
121 | params.append('%d years' % self.years) | |
106 | params.append("%d years" % self.years) | |
122 | 107 | if self.months: |
123 | 108 | fmt = "%d months" |
124 | 109 | if self.months <= 1: |
125 | 110 | fmt = "%d month" |
126 | 111 | params.append(fmt % self.months) |
127 | 112 | params.append(str(self.tdelta)) |
128 | return ', '.join(params) | |
113 | return ", ".join(params) | |
129 | 114 | |
130 | 115 | def __repr__(self): |
131 | ''' | |
116 | """ | |
132 | 117 | Return a string suitable for repr(x) calls. |
133 | ''' | |
118 | """ | |
134 | 119 | return "%s.%s(%d, %d, %d, years=%d, months=%d)" % ( |
135 | self.__class__.__module__, self.__class__.__name__, | |
136 | self.tdelta.days, self.tdelta.seconds, | |
137 | self.tdelta.microseconds, self.years, self.months) | |
120 | self.__class__.__module__, | |
121 | self.__class__.__name__, | |
122 | self.tdelta.days, | |
123 | self.tdelta.seconds, | |
124 | self.tdelta.microseconds, | |
125 | self.years, | |
126 | self.months, | |
127 | ) | |
138 | 128 | |
139 | 129 | def __hash__(self): |
140 | ''' | |
130 | """ | |
141 | 131 | Return a hash of this instance so that it can be used in, for |
142 | 132 | example, dicts and sets. |
143 | ''' | |
133 | """ | |
144 | 134 | return hash((self.tdelta, self.months, self.years)) |
145 | 135 | |
146 | 136 | def __neg__(self): |
154 | 144 | return negduration |
155 | 145 | |
156 | 146 | def __add__(self, other): |
157 | ''' | |
147 | """ | |
158 | 148 | Durations can be added with Duration, timedelta, date and datetime |
159 | 149 | objects. |
160 | ''' | |
150 | """ | |
161 | 151 | if isinstance(other, Duration): |
162 | newduration = Duration(years=self.years + other.years, | |
163 | months=self.months + other.months) | |
152 | newduration = Duration( | |
153 | years=self.years + other.years, months=self.months + other.months | |
154 | ) | |
164 | 155 | newduration.tdelta = self.tdelta + other.tdelta |
165 | 156 | return newduration |
166 | 157 | try: |
167 | 158 | # try anything that looks like a date or datetime |
168 | 159 | # 'other' has attributes year, month, day |
169 | 160 | # and relies on 'timedelta + other' being implemented |
170 | if (not(float(self.years).is_integer() and | |
171 | float(self.months).is_integer())): | |
172 | raise ValueError('fractional years or months not supported' | |
173 | ' for date calculations') | |
161 | if not (float(self.years).is_integer() and float(self.months).is_integer()): | |
162 | raise ValueError( | |
163 | "fractional years or months not supported" " for date calculations" | |
164 | ) | |
174 | 165 | newmonth = other.month + self.months |
175 | 166 | carry, newmonth = fquotmod(newmonth, 1, 13) |
176 | 167 | newyear = other.year + self.years + carry |
203 | 194 | |
204 | 195 | def __mul__(self, other): |
205 | 196 | if isinstance(other, int): |
206 | newduration = Duration( | |
207 | years=self.years * other, | |
208 | months=self.months * other) | |
197 | newduration = Duration(years=self.years * other, months=self.months * other) | |
209 | 198 | newduration.tdelta = self.tdelta * other |
210 | 199 | return newduration |
211 | 200 | return NotImplemented |
213 | 202 | __rmul__ = __mul__ |
214 | 203 | |
215 | 204 | def __sub__(self, other): |
216 | ''' | |
205 | """ | |
217 | 206 | It is possible to subtract Duration and timedelta objects from Duration |
218 | 207 | objects. |
219 | ''' | |
208 | """ | |
220 | 209 | if isinstance(other, Duration): |
221 | newduration = Duration(years=self.years - other.years, | |
222 | months=self.months - other.months) | |
210 | newduration = Duration( | |
211 | years=self.years - other.years, months=self.months - other.months | |
212 | ) | |
223 | 213 | newduration.tdelta = self.tdelta - other.tdelta |
224 | 214 | return newduration |
225 | 215 | try: |
233 | 223 | return NotImplemented |
234 | 224 | |
235 | 225 | def __rsub__(self, other): |
236 | ''' | |
226 | """ | |
237 | 227 | It is possible to subtract Duration objecs from date, datetime and |
238 | 228 | timedelta objects. |
239 | 229 | |
245 | 235 | the stdlib we can just do: |
246 | 236 | return -self + other |
247 | 237 | instead of all the current code |
248 | ''' | |
238 | """ | |
249 | 239 | if isinstance(other, timedelta): |
250 | 240 | tmpdur = Duration() |
251 | 241 | tmpdur.tdelta = other |
253 | 243 | try: |
254 | 244 | # check if other behaves like a date/datetime object |
255 | 245 | # does it have year, month, day and replace? |
256 | if (not(float(self.years).is_integer() and | |
257 | float(self.months).is_integer())): | |
258 | raise ValueError('fractional years or months not supported' | |
259 | ' for date calculations') | |
246 | if not (float(self.years).is_integer() and float(self.months).is_integer()): | |
247 | raise ValueError( | |
248 | "fractional years or months not supported" " for date calculations" | |
249 | ) | |
260 | 250 | newmonth = other.month - self.months |
261 | 251 | carry, newmonth = fquotmod(newmonth, 1, 13) |
262 | 252 | newyear = other.year - self.years + carry |
275 | 265 | return NotImplemented |
276 | 266 | |
277 | 267 | def __eq__(self, other): |
278 | ''' | |
268 | """ | |
279 | 269 | If the years, month part and the timedelta part are both equal, then |
280 | 270 | the two Durations are considered equal. |
281 | ''' | |
271 | """ | |
282 | 272 | if isinstance(other, Duration): |
283 | if (((self.years * 12 + self.months) == | |
284 | (other.years * 12 + other.months) and | |
285 | self.tdelta == other.tdelta)): | |
273 | if (self.years * 12 + self.months) == ( | |
274 | other.years * 12 + other.months | |
275 | ) and self.tdelta == other.tdelta: | |
286 | 276 | return True |
287 | 277 | return False |
288 | 278 | # check if other con be compared against timedelta object |
292 | 282 | return False |
293 | 283 | |
294 | 284 | def __ne__(self, other): |
295 | ''' | |
285 | """ | |
296 | 286 | If the years, month part or the timedelta part is not equal, then |
297 | 287 | the two Durations are considered not equal. |
298 | ''' | |
288 | """ | |
299 | 289 | if isinstance(other, Duration): |
300 | if (((self.years * 12 + self.months) != | |
301 | (other.years * 12 + other.months) or | |
302 | self.tdelta != other.tdelta)): | |
290 | if (self.years * 12 + self.months) != ( | |
291 | other.years * 12 + other.months | |
292 | ) or self.tdelta != other.tdelta: | |
303 | 293 | return True |
304 | 294 | return False |
305 | 295 | # check if other can be compared against timedelta object |
309 | 299 | return True |
310 | 300 | |
311 | 301 | def totimedelta(self, start=None, end=None): |
312 | ''' | |
302 | """ | |
313 | 303 | Convert this duration into a timedelta object. |
314 | 304 | |
315 | 305 | This method requires a start datetime or end datetimem, but raises |
316 | 306 | an exception if both are given. |
317 | ''' | |
307 | """ | |
318 | 308 | if start is None and end is None: |
319 | 309 | raise ValueError("start or end required") |
320 | 310 | if start is not None and end is not None: |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | This modules provides a method to parse an ISO 8601:2004 date string to a |
28 | 2 | python datetime.date instance. |
29 | 3 | |
30 | 4 | It supports all basic, extended and expanded formats as described in the ISO |
31 | 5 | standard. The only limitations it has, are given by the Python datetime.date |
32 | 6 | implementation, which does not support dates before 0001-01-01. |
33 | ''' | |
7 | """ | |
34 | 8 | import re |
35 | 9 | from datetime import date, timedelta |
36 | 10 | |
45 | 19 | |
46 | 20 | |
47 | 21 | def build_date_regexps(yeardigits=4, expanded=False): |
48 | ''' | |
22 | """ | |
49 | 23 | Compile set of regular expressions to parse ISO dates. The expressions will |
50 | 24 | be created only if they are not already in REGEX_CACHE. |
51 | 25 | |
55 | 29 | ISO 8601 allows more than 4 digit years, on prior agreement, but then a +/- |
56 | 30 | sign is required (expanded format). To support +/- sign for 4 digit years, |
57 | 31 | the expanded parameter needs to be set to True. |
58 | ''' | |
32 | """ | |
59 | 33 | if yeardigits != 4: |
60 | 34 | expanded = True |
61 | 35 | if (yeardigits, expanded) not in DATE_REGEX_CACHE: |
66 | 40 | sign = 1 |
67 | 41 | else: |
68 | 42 | sign = 0 |
43 | ||
44 | def add_re(regex_text): | |
45 | cache_entry.append(re.compile(r"\A" + regex_text + r"\Z")) | |
46 | ||
69 | 47 | # 1. complete dates: |
70 | 48 | # YYYY-MM-DD or +- YYYYYY-MM-DD... extended date format |
71 | cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
72 | r"-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})" | |
73 | % (sign, yeardigits))) | |
49 | add_re( | |
50 | r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
51 | r"-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})" % (sign, yeardigits) | |
52 | ) | |
74 | 53 | # YYYYMMDD or +- YYYYYYMMDD... basic date format |
75 | cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
76 | r"(?P<month>[0-9]{2})(?P<day>[0-9]{2})" | |
77 | % (sign, yeardigits))) | |
54 | add_re( | |
55 | r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
56 | r"(?P<month>[0-9]{2})(?P<day>[0-9]{2})" % (sign, yeardigits) | |
57 | ) | |
78 | 58 | # 2. complete week dates: |
79 | 59 | # YYYY-Www-D or +-YYYYYY-Www-D ... extended week date |
80 | cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
81 | r"-W(?P<week>[0-9]{2})-(?P<day>[0-9]{1})" | |
82 | % (sign, yeardigits))) | |
60 | add_re( | |
61 | r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
62 | r"-W(?P<week>[0-9]{2})-(?P<day>[0-9]{1})" % (sign, yeardigits) | |
63 | ) | |
83 | 64 | # YYYYWwwD or +-YYYYYYWwwD ... basic week date |
84 | cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W" | |
85 | r"(?P<week>[0-9]{2})(?P<day>[0-9]{1})" | |
86 | % (sign, yeardigits))) | |
65 | add_re( | |
66 | r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W" | |
67 | r"(?P<week>[0-9]{2})(?P<day>[0-9]{1})" % (sign, yeardigits) | |
68 | ) | |
87 | 69 | # 3. ordinal dates: |
88 | 70 | # YYYY-DDD or +-YYYYYY-DDD ... extended format |
89 | cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
90 | r"-(?P<day>[0-9]{3})" | |
91 | % (sign, yeardigits))) | |
71 | add_re( | |
72 | r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
73 | r"-(?P<day>[0-9]{3})" % (sign, yeardigits) | |
74 | ) | |
92 | 75 | # YYYYDDD or +-YYYYYYDDD ... basic format |
93 | cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
94 | r"(?P<day>[0-9]{3})" | |
95 | % (sign, yeardigits))) | |
76 | add_re( | |
77 | r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
78 | r"(?P<day>[0-9]{3})" % (sign, yeardigits) | |
79 | ) | |
96 | 80 | # 4. week dates: |
97 | 81 | # YYYY-Www or +-YYYYYY-Www ... extended reduced accuracy week date |
98 | cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
99 | r"-W(?P<week>[0-9]{2})" | |
100 | % (sign, yeardigits))) | |
82 | # 4. week dates: | |
83 | # YYYY-Www or +-YYYYYY-Www ... extended reduced accuracy week date | |
84 | add_re( | |
85 | r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
86 | r"-W(?P<week>[0-9]{2})" % (sign, yeardigits) | |
87 | ) | |
101 | 88 | # YYYYWww or +-YYYYYYWww ... basic reduced accuracy week date |
102 | cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W" | |
103 | r"(?P<week>[0-9]{2})" | |
104 | % (sign, yeardigits))) | |
89 | add_re( | |
90 | r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W" | |
91 | r"(?P<week>[0-9]{2})" % (sign, yeardigits) | |
92 | ) | |
105 | 93 | # 5. month dates: |
106 | 94 | # YYY-MM or +-YYYYYY-MM ... reduced accuracy specific month |
107 | cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
108 | r"-(?P<month>[0-9]{2})" | |
109 | % (sign, yeardigits))) | |
95 | # 5. month dates: | |
96 | # YYY-MM or +-YYYYYY-MM ... reduced accuracy specific month | |
97 | add_re( | |
98 | r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
99 | r"-(?P<month>[0-9]{2})" % (sign, yeardigits) | |
100 | ) | |
110 | 101 | # YYYMM or +-YYYYYYMM ... basic incomplete month date format |
111 | cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
112 | r"(?P<month>[0-9]{2})" | |
113 | % (sign, yeardigits))) | |
102 | add_re( | |
103 | r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
104 | r"(?P<month>[0-9]{2})" % (sign, yeardigits) | |
105 | ) | |
114 | 106 | # 6. year dates: |
115 | 107 | # YYYY or +-YYYYYY ... reduced accuracy specific year |
116 | cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" | |
117 | % (sign, yeardigits))) | |
108 | add_re(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" % (sign, yeardigits)) | |
118 | 109 | # 7. century dates: |
119 | 110 | # YY or +-YYYY ... reduced accuracy specific century |
120 | cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}" | |
121 | r"(?P<century>[0-9]{%d})" | |
122 | % (sign, yeardigits - 2))) | |
111 | add_re(r"(?P<sign>[+-]){%d}" r"(?P<century>[0-9]{%d})" % (sign, yeardigits - 2)) | |
123 | 112 | |
124 | 113 | DATE_REGEX_CACHE[(yeardigits, expanded)] = cache_entry |
125 | 114 | return DATE_REGEX_CACHE[(yeardigits, expanded)] |
126 | 115 | |
127 | 116 | |
128 | def parse_date( | |
129 | datestring, | |
130 | yeardigits=4, expanded=False, defaultmonth=1, defaultday=1): | |
131 | ''' | |
117 | def parse_date(datestring, yeardigits=4, expanded=False, defaultmonth=1, defaultday=1): | |
118 | """ | |
132 | 119 | Parse an ISO 8601 date string into a datetime.date object. |
133 | 120 | |
134 | 121 | As the datetime.date implementation is limited to dates starting from |
161 | 148 | @return: a datetime.date instance represented by datestring |
162 | 149 | @raise ISO8601Error: if this function can not parse the datestring |
163 | 150 | @raise ValueError: if datestring can not be represented by datetime.date |
164 | ''' | |
151 | """ | |
165 | 152 | if yeardigits != 4: |
166 | 153 | expanded = True |
167 | 154 | isodates = build_date_regexps(yeardigits, expanded) |
171 | 158 | groups = match.groupdict() |
172 | 159 | # sign, century, year, month, week, day, |
173 | 160 | # FIXME: negative dates not possible with python standard types |
174 | sign = (groups['sign'] == '-' and -1) or 1 | |
175 | if 'century' in groups: | |
161 | sign = (groups["sign"] == "-" and -1) or 1 | |
162 | if "century" in groups: | |
176 | 163 | return date( |
177 | sign * (int(groups['century']) * 100 + 1), | |
178 | defaultmonth, defaultday) | |
179 | if 'month' not in groups: # weekdate or ordinal date | |
180 | ret = date(sign * int(groups['year']), 1, 1) | |
181 | if 'week' in groups: | |
164 | sign * (int(groups["century"]) * 100 + 1), defaultmonth, defaultday | |
165 | ) | |
166 | if "month" not in groups: # weekdate or ordinal date | |
167 | ret = date(sign * int(groups["year"]), 1, 1) | |
168 | if "week" in groups: | |
182 | 169 | isotuple = ret.isocalendar() |
183 | if 'day' in groups: | |
184 | days = int(groups['day'] or 1) | |
170 | if "day" in groups: | |
171 | days = int(groups["day"] or 1) | |
185 | 172 | else: |
186 | 173 | days = 1 |
187 | 174 | # if first week in year, do weeks-1 |
188 | return ret + timedelta(weeks=int(groups['week']) - | |
189 | (((isotuple[1] == 1) and 1) or 0), | |
190 | days=-isotuple[2] + days) | |
191 | elif 'day' in groups: # ordinal date | |
192 | return ret + timedelta(days=int(groups['day']) - 1) | |
175 | return ret + timedelta( | |
176 | weeks=int(groups["week"]) - (((isotuple[1] == 1) and 1) or 0), | |
177 | days=-isotuple[2] + days, | |
178 | ) | |
179 | elif "day" in groups: # ordinal date | |
180 | return ret + timedelta(days=int(groups["day"]) - 1) | |
193 | 181 | else: # year date |
194 | 182 | return ret.replace(month=defaultmonth, day=defaultday) |
195 | 183 | # year-, month-, or complete date |
196 | if 'day' not in groups or groups['day'] is None: | |
184 | if "day" not in groups or groups["day"] is None: | |
197 | 185 | day = defaultday |
198 | 186 | else: |
199 | day = int(groups['day']) | |
200 | return date(sign * int(groups['year']), | |
201 | int(groups['month']) or defaultmonth, day) | |
202 | raise ISO8601Error('Unrecognised ISO 8601 date format: %r' % datestring) | |
187 | day = int(groups["day"]) | |
188 | return date( | |
189 | sign * int(groups["year"]), int(groups["month"]) or defaultmonth, day | |
190 | ) | |
191 | raise ISO8601Error("Unrecognised ISO 8601 date format: %r" % datestring) | |
203 | 192 | |
204 | 193 | |
205 | 194 | def date_isoformat(tdate, format=DATE_EXT_COMPLETE, yeardigits=4): |
206 | ''' | |
195 | """ | |
207 | 196 | Format date strings. |
208 | 197 | |
209 | 198 | This method is just a wrapper around isodate.isostrf.strftime and uses |
210 | 199 | Date-Extended-Complete as default format. |
211 | ''' | |
200 | """ | |
212 | 201 | return strftime(tdate, format, yeardigits) |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | This module defines a method to parse an ISO 8601:2004 date time string. |
28 | 2 | |
29 | 3 | For this job it uses the parse_date and parse_time methods defined in date |
30 | 4 | and time module. |
31 | ''' | |
5 | """ | |
32 | 6 | from datetime import datetime |
33 | 7 | |
34 | 8 | from isodate.isostrf import strftime |
39 | 13 | |
40 | 14 | |
41 | 15 | def parse_datetime(datetimestring): |
42 | ''' | |
16 | """ | |
43 | 17 | Parses ISO 8601 date-times into datetime.datetime objects. |
44 | 18 | |
45 | 19 | This function uses parse_date and parse_time to do the job, so it allows |
46 | 20 | more combinations of date and time representations, than the actual |
47 | 21 | ISO 8601:2004 standard allows. |
48 | ''' | |
22 | """ | |
49 | 23 | try: |
50 | datestring, timestring = datetimestring.split('T') | |
24 | datestring, timestring = datetimestring.split("T") | |
51 | 25 | except ValueError: |
52 | raise ISO8601Error("ISO 8601 time designator 'T' missing. Unable to" | |
53 | " parse datetime string %r" % datetimestring) | |
26 | raise ISO8601Error( | |
27 | "ISO 8601 time designator 'T' missing. Unable to" | |
28 | " parse datetime string %r" % datetimestring | |
29 | ) | |
54 | 30 | tmpdate = parse_date(datestring) |
55 | 31 | tmptime = parse_time(timestring) |
56 | 32 | return datetime.combine(tmpdate, tmptime) |
57 | 33 | |
58 | 34 | |
59 | def datetime_isoformat(tdt, format=DATE_EXT_COMPLETE + 'T' + | |
60 | TIME_EXT_COMPLETE + TZ_EXT): | |
61 | ''' | |
35 | def datetime_isoformat( | |
36 | tdt, format=DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT | |
37 | ): | |
38 | """ | |
62 | 39 | Format datetime strings. |
63 | 40 | |
64 | 41 | This method is just a wrapper around isodate.isostrf.strftime and uses |
65 | 42 | Extended-Complete as default format. |
66 | ''' | |
43 | """ | |
67 | 44 | return strftime(tdt, format) |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | This module provides an ISO 8601:2004 duration parser. |
28 | 2 | |
29 | 3 | It also provides a wrapper to strftime. This wrapper makes it easier to |
30 | 4 | format timedelta or Duration instances as ISO conforming strings. |
31 | ''' | |
5 | """ | |
32 | 6 | from datetime import timedelta |
33 | 7 | from decimal import Decimal |
34 | 8 | import re |
35 | ||
36 | from six import string_types | |
37 | 9 | |
38 | 10 | from isodate.duration import Duration |
39 | 11 | from isodate.isoerror import ISO8601Error |
49 | 21 | r"(?P<days>[0-9]+([,.][0-9]+)?D)?" |
50 | 22 | r"((?P<separator>T)(?P<hours>[0-9]+([,.][0-9]+)?H)?" |
51 | 23 | r"(?P<minutes>[0-9]+([,.][0-9]+)?M)?" |
52 | r"(?P<seconds>[0-9]+([,.][0-9]+)?S)?)?$") | |
53 | # regular expression to parse ISO duartion strings. | |
24 | r"(?P<seconds>[0-9]+([,.][0-9]+)?S)?)?$" | |
25 | ) | |
26 | # regular expression to parse ISO duration strings. | |
54 | 27 | |
55 | 28 | |
56 | def parse_duration(datestring): | |
29 | def parse_duration(datestring, as_timedelta_if_possible=True): | |
57 | 30 | """ |
58 | 31 | Parses an ISO 8601 durations into datetime.timedelta or Duration objects. |
59 | 32 | |
81 | 54 | The alternative format does not support durations with years, months or |
82 | 55 | days set to 0. |
83 | 56 | """ |
84 | if not isinstance(datestring, string_types): | |
57 | if not isinstance(datestring, str): | |
85 | 58 | raise TypeError("Expecting a string %r" % datestring) |
86 | 59 | match = ISO8601_PERIOD_REGEX.match(datestring) |
87 | 60 | if not match: |
88 | 61 | # try alternative format: |
89 | 62 | if datestring.startswith("P"): |
90 | 63 | durdt = parse_datetime(datestring[1:]) |
91 | if durdt.year != 0 or durdt.month != 0: | |
64 | if as_timedelta_if_possible and durdt.year == 0 and durdt.month == 0: | |
65 | # FIXME: currently not possible in alternative format | |
66 | # create timedelta | |
67 | ret = timedelta( | |
68 | days=durdt.day, | |
69 | seconds=durdt.second, | |
70 | microseconds=durdt.microsecond, | |
71 | minutes=durdt.minute, | |
72 | hours=durdt.hour, | |
73 | ) | |
74 | else: | |
92 | 75 | # create Duration |
93 | ret = Duration(days=durdt.day, seconds=durdt.second, | |
94 | microseconds=durdt.microsecond, | |
95 | minutes=durdt.minute, hours=durdt.hour, | |
96 | months=durdt.month, years=durdt.year) | |
97 | else: # FIXME: currently not possible in alternative format | |
98 | # create timedelta | |
99 | ret = timedelta(days=durdt.day, seconds=durdt.second, | |
100 | microseconds=durdt.microsecond, | |
101 | minutes=durdt.minute, hours=durdt.hour) | |
76 | ret = Duration( | |
77 | days=durdt.day, | |
78 | seconds=durdt.second, | |
79 | microseconds=durdt.microsecond, | |
80 | minutes=durdt.minute, | |
81 | hours=durdt.hour, | |
82 | months=durdt.month, | |
83 | years=durdt.year, | |
84 | ) | |
102 | 85 | return ret |
103 | 86 | raise ISO8601Error("Unable to parse duration string %r" % datestring) |
104 | 87 | groups = match.groupdict() |
105 | 88 | for key, val in groups.items(): |
106 | if key not in ('separator', 'sign'): | |
89 | if key not in ("separator", "sign"): | |
107 | 90 | if val is None: |
108 | 91 | groups[key] = "0n" |
109 | 92 | # print groups[key] |
110 | if key in ('years', 'months'): | |
111 | groups[key] = Decimal(groups[key][:-1].replace(',', '.')) | |
93 | if key in ("years", "months"): | |
94 | groups[key] = Decimal(groups[key][:-1].replace(",", ".")) | |
112 | 95 | else: |
113 | 96 | # these values are passed into a timedelta object, |
114 | 97 | # which works with floats. |
115 | groups[key] = float(groups[key][:-1].replace(',', '.')) | |
116 | if groups["years"] == 0 and groups["months"] == 0: | |
117 | ret = timedelta(days=groups["days"], hours=groups["hours"], | |
118 | minutes=groups["minutes"], seconds=groups["seconds"], | |
119 | weeks=groups["weeks"]) | |
120 | if groups["sign"] == '-': | |
98 | groups[key] = float(groups[key][:-1].replace(",", ".")) | |
99 | if as_timedelta_if_possible and groups["years"] == 0 and groups["months"] == 0: | |
100 | ret = timedelta( | |
101 | days=groups["days"], | |
102 | hours=groups["hours"], | |
103 | minutes=groups["minutes"], | |
104 | seconds=groups["seconds"], | |
105 | weeks=groups["weeks"], | |
106 | ) | |
107 | if groups["sign"] == "-": | |
121 | 108 | ret = timedelta(0) - ret |
122 | 109 | else: |
123 | ret = Duration(years=groups["years"], months=groups["months"], | |
124 | days=groups["days"], hours=groups["hours"], | |
125 | minutes=groups["minutes"], seconds=groups["seconds"], | |
126 | weeks=groups["weeks"]) | |
127 | if groups["sign"] == '-': | |
110 | ret = Duration( | |
111 | years=groups["years"], | |
112 | months=groups["months"], | |
113 | days=groups["days"], | |
114 | hours=groups["hours"], | |
115 | minutes=groups["minutes"], | |
116 | seconds=groups["seconds"], | |
117 | weeks=groups["weeks"], | |
118 | ) | |
119 | if groups["sign"] == "-": | |
128 | 120 | ret = Duration(0) - ret |
129 | 121 | return ret |
130 | 122 | |
131 | 123 | |
132 | 124 | def duration_isoformat(tduration, format=D_DEFAULT): |
133 | ''' | |
125 | """ | |
134 | 126 | Format duration strings. |
135 | 127 | |
136 | 128 | This method is just a wrapper around isodate.isostrf.strftime and uses |
137 | 129 | P%P (D_DEFAULT) as default format. |
138 | ''' | |
130 | """ | |
139 | 131 | # TODO: implement better decision for negative Durations. |
140 | 132 | # should be done in Duration class in consistent way with timedelta. |
141 | if (((isinstance(tduration, Duration) and | |
142 | (tduration.years < 0 or tduration.months < 0 or | |
143 | tduration.tdelta < timedelta(0))) or | |
144 | (isinstance(tduration, timedelta) and | |
145 | (tduration < timedelta(0))))): | |
146 | ret = '-' | |
133 | if ( | |
134 | isinstance(tduration, Duration) | |
135 | and ( | |
136 | tduration.years < 0 | |
137 | or tduration.months < 0 | |
138 | or tduration.tdelta < timedelta(0) | |
139 | ) | |
140 | ) or (isinstance(tduration, timedelta) and (tduration < timedelta(0))): | |
141 | ret = "-" | |
147 | 142 | else: |
148 | ret = '' | |
143 | ret = "" | |
149 | 144 | ret += strftime(tduration, format) |
150 | 145 | return ret |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | This module defines all exception classes in the whole package. |
28 | ''' | |
2 | """ | |
29 | 3 | |
30 | 4 | |
31 | 5 | class ISO8601Error(ValueError): |
32 | '''Raised when the given ISO string can not be parsed.''' | |
6 | """Raised when the given ISO string can not be parsed.""" |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | 0 | """ |
27 | 1 | This module provides an alternative strftime method. |
28 | 2 | |
39 | 13 | from isodate.isotzinfo import tz_isoformat |
40 | 14 | |
41 | 15 | # Date specific format strings |
42 | DATE_BAS_COMPLETE = '%Y%m%d' | |
43 | DATE_EXT_COMPLETE = '%Y-%m-%d' | |
44 | DATE_BAS_WEEK_COMPLETE = '%YW%W%w' | |
45 | DATE_EXT_WEEK_COMPLETE = '%Y-W%W-%w' | |
46 | DATE_BAS_ORD_COMPLETE = '%Y%j' | |
47 | DATE_EXT_ORD_COMPLETE = '%Y-%j' | |
48 | DATE_BAS_WEEK = '%YW%W' | |
49 | DATE_EXT_WEEK = '%Y-W%W' | |
50 | DATE_BAS_MONTH = '%Y%m' | |
51 | DATE_EXT_MONTH = '%Y-%m' | |
52 | DATE_YEAR = '%Y' | |
53 | DATE_CENTURY = '%C' | |
16 | DATE_BAS_COMPLETE = "%Y%m%d" | |
17 | DATE_EXT_COMPLETE = "%Y-%m-%d" | |
18 | DATE_BAS_WEEK_COMPLETE = "%YW%W%w" | |
19 | DATE_EXT_WEEK_COMPLETE = "%Y-W%W-%w" | |
20 | DATE_BAS_ORD_COMPLETE = "%Y%j" | |
21 | DATE_EXT_ORD_COMPLETE = "%Y-%j" | |
22 | DATE_BAS_WEEK = "%YW%W" | |
23 | DATE_EXT_WEEK = "%Y-W%W" | |
24 | DATE_BAS_MONTH = "%Y%m" | |
25 | DATE_EXT_MONTH = "%Y-%m" | |
26 | DATE_YEAR = "%Y" | |
27 | DATE_CENTURY = "%C" | |
54 | 28 | |
55 | 29 | # Time specific format strings |
56 | TIME_BAS_COMPLETE = '%H%M%S' | |
57 | TIME_EXT_COMPLETE = '%H:%M:%S' | |
58 | TIME_BAS_MINUTE = '%H%M' | |
59 | TIME_EXT_MINUTE = '%H:%M' | |
60 | TIME_HOUR = '%H' | |
30 | TIME_BAS_COMPLETE = "%H%M%S" | |
31 | TIME_EXT_COMPLETE = "%H:%M:%S" | |
32 | TIME_BAS_MINUTE = "%H%M" | |
33 | TIME_EXT_MINUTE = "%H:%M" | |
34 | TIME_HOUR = "%H" | |
61 | 35 | |
62 | 36 | # Time zone formats |
63 | TZ_BAS = '%z' | |
64 | TZ_EXT = '%Z' | |
65 | TZ_HOUR = '%h' | |
37 | TZ_BAS = "%z" | |
38 | TZ_EXT = "%Z" | |
39 | TZ_HOUR = "%h" | |
66 | 40 | |
67 | 41 | # DateTime formats |
68 | DT_EXT_COMPLETE = DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + TZ_EXT | |
69 | DT_BAS_COMPLETE = DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE + TZ_BAS | |
70 | DT_EXT_ORD_COMPLETE = DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_COMPLETE + TZ_EXT | |
71 | DT_BAS_ORD_COMPLETE = DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_COMPLETE + TZ_BAS | |
72 | DT_EXT_WEEK_COMPLETE = (DATE_EXT_WEEK_COMPLETE + 'T' + | |
73 | TIME_EXT_COMPLETE + TZ_EXT) | |
74 | DT_BAS_WEEK_COMPLETE = (DATE_BAS_WEEK_COMPLETE + 'T' + | |
75 | TIME_BAS_COMPLETE + TZ_BAS) | |
42 | DT_EXT_COMPLETE = DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT | |
43 | DT_BAS_COMPLETE = DATE_BAS_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS | |
44 | DT_EXT_ORD_COMPLETE = DATE_EXT_ORD_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT | |
45 | DT_BAS_ORD_COMPLETE = DATE_BAS_ORD_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS | |
46 | DT_EXT_WEEK_COMPLETE = DATE_EXT_WEEK_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT | |
47 | DT_BAS_WEEK_COMPLETE = DATE_BAS_WEEK_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS | |
76 | 48 | |
77 | 49 | # Duration formts |
78 | D_DEFAULT = 'P%P' | |
79 | D_WEEK = 'P%p' | |
80 | D_ALT_EXT = 'P' + DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE | |
81 | D_ALT_BAS = 'P' + DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE | |
82 | D_ALT_EXT_ORD = 'P' + DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_COMPLETE | |
83 | D_ALT_BAS_ORD = 'P' + DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_COMPLETE | |
50 | D_DEFAULT = "P%P" | |
51 | D_WEEK = "P%p" | |
52 | D_ALT_EXT = "P" + DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE | |
53 | D_ALT_BAS = "P" + DATE_BAS_COMPLETE + "T" + TIME_BAS_COMPLETE | |
54 | D_ALT_EXT_ORD = "P" + DATE_EXT_ORD_COMPLETE + "T" + TIME_EXT_COMPLETE | |
55 | D_ALT_BAS_ORD = "P" + DATE_BAS_ORD_COMPLETE + "T" + TIME_BAS_COMPLETE | |
84 | 56 | |
85 | STRF_DT_MAP = {'%d': lambda tdt, yds: '%02d' % tdt.day, | |
86 | '%f': lambda tdt, yds: '%06d' % tdt.microsecond, | |
87 | '%H': lambda tdt, yds: '%02d' % tdt.hour, | |
88 | '%j': lambda tdt, yds: '%03d' % (tdt.toordinal() - | |
89 | date(tdt.year, | |
90 | 1, 1).toordinal() + | |
91 | 1), | |
92 | '%m': lambda tdt, yds: '%02d' % tdt.month, | |
93 | '%M': lambda tdt, yds: '%02d' % tdt.minute, | |
94 | '%S': lambda tdt, yds: '%02d' % tdt.second, | |
95 | '%w': lambda tdt, yds: '%1d' % tdt.isoweekday(), | |
96 | '%W': lambda tdt, yds: '%02d' % tdt.isocalendar()[1], | |
97 | '%Y': lambda tdt, yds: (((yds != 4) and '+') or '') + | |
98 | (('%%0%dd' % yds) % tdt.year), | |
99 | '%C': lambda tdt, yds: (((yds != 4) and '+') or '') + | |
100 | (('%%0%dd' % (yds - 2)) % | |
101 | (tdt.year / 100)), | |
102 | '%h': lambda tdt, yds: tz_isoformat(tdt, '%h'), | |
103 | '%Z': lambda tdt, yds: tz_isoformat(tdt, '%Z'), | |
104 | '%z': lambda tdt, yds: tz_isoformat(tdt, '%z'), | |
105 | '%%': lambda tdt, yds: '%'} | |
57 | STRF_DT_MAP = { | |
58 | "%d": lambda tdt, yds: "%02d" % tdt.day, | |
59 | "%f": lambda tdt, yds: "%06d" % tdt.microsecond, | |
60 | "%H": lambda tdt, yds: "%02d" % tdt.hour, | |
61 | "%j": lambda tdt, yds: "%03d" | |
62 | % (tdt.toordinal() - date(tdt.year, 1, 1).toordinal() + 1), | |
63 | "%m": lambda tdt, yds: "%02d" % tdt.month, | |
64 | "%M": lambda tdt, yds: "%02d" % tdt.minute, | |
65 | "%S": lambda tdt, yds: "%02d" % tdt.second, | |
66 | "%w": lambda tdt, yds: "%1d" % tdt.isoweekday(), | |
67 | "%W": lambda tdt, yds: "%02d" % tdt.isocalendar()[1], | |
68 | "%Y": lambda tdt, yds: (((yds != 4) and "+") or "") + (("%%0%dd" % yds) % tdt.year), | |
69 | "%C": lambda tdt, yds: (((yds != 4) and "+") or "") | |
70 | + (("%%0%dd" % (yds - 2)) % (tdt.year / 100)), | |
71 | "%h": lambda tdt, yds: tz_isoformat(tdt, "%h"), | |
72 | "%Z": lambda tdt, yds: tz_isoformat(tdt, "%Z"), | |
73 | "%z": lambda tdt, yds: tz_isoformat(tdt, "%z"), | |
74 | "%%": lambda tdt, yds: "%", | |
75 | } | |
106 | 76 | |
107 | STRF_D_MAP = {'%d': lambda tdt, yds: '%02d' % tdt.days, | |
108 | '%f': lambda tdt, yds: '%06d' % tdt.microseconds, | |
109 | '%H': lambda tdt, yds: '%02d' % (tdt.seconds / 60 / 60), | |
110 | '%m': lambda tdt, yds: '%02d' % tdt.months, | |
111 | '%M': lambda tdt, yds: '%02d' % ((tdt.seconds / 60) % 60), | |
112 | '%S': lambda tdt, yds: '%02d' % (tdt.seconds % 60), | |
113 | '%W': lambda tdt, yds: '%02d' % (abs(tdt.days / 7)), | |
114 | '%Y': lambda tdt, yds: (((yds != 4) and '+') or '') + | |
115 | (('%%0%dd' % yds) % tdt.years), | |
116 | '%C': lambda tdt, yds: (((yds != 4) and '+') or '') + | |
117 | (('%%0%dd' % (yds - 2)) % | |
118 | (tdt.years / 100)), | |
119 | '%%': lambda tdt, yds: '%'} | |
77 | STRF_D_MAP = { | |
78 | "%d": lambda tdt, yds: "%02d" % tdt.days, | |
79 | "%f": lambda tdt, yds: "%06d" % tdt.microseconds, | |
80 | "%H": lambda tdt, yds: "%02d" % (tdt.seconds / 60 / 60), | |
81 | "%m": lambda tdt, yds: "%02d" % tdt.months, | |
82 | "%M": lambda tdt, yds: "%02d" % ((tdt.seconds / 60) % 60), | |
83 | "%S": lambda tdt, yds: "%02d" % (tdt.seconds % 60), | |
84 | "%W": lambda tdt, yds: "%02d" % (abs(tdt.days / 7)), | |
85 | "%Y": lambda tdt, yds: (((yds != 4) and "+") or "") | |
86 | + (("%%0%dd" % yds) % tdt.years), | |
87 | "%C": lambda tdt, yds: (((yds != 4) and "+") or "") | |
88 | + (("%%0%dd" % (yds - 2)) % (tdt.years / 100)), | |
89 | "%%": lambda tdt, yds: "%", | |
90 | } | |
120 | 91 | |
121 | 92 | |
122 | 93 | def _strfduration(tdt, format, yeardigits=4): |
123 | ''' | |
94 | """ | |
124 | 95 | this is the work method for timedelta and Duration instances. |
125 | 96 | |
126 | 97 | see strftime for more details. |
127 | ''' | |
98 | """ | |
99 | ||
128 | 100 | def repl(match): |
129 | ''' | |
101 | """ | |
130 | 102 | lookup format command and return corresponding replacement. |
131 | ''' | |
103 | """ | |
132 | 104 | if match.group(0) in STRF_D_MAP: |
133 | 105 | return STRF_D_MAP[match.group(0)](tdt, yeardigits) |
134 | elif match.group(0) == '%P': | |
106 | elif match.group(0) == "%P": | |
135 | 107 | ret = [] |
136 | 108 | if isinstance(tdt, Duration): |
137 | 109 | if tdt.years: |
138 | ret.append('%sY' % abs(tdt.years)) | |
110 | ret.append("%sY" % abs(tdt.years)) | |
139 | 111 | if tdt.months: |
140 | ret.append('%sM' % abs(tdt.months)) | |
141 | usecs = abs((tdt.days * 24 * 60 * 60 + tdt.seconds) * 1000000 + | |
142 | tdt.microseconds) | |
112 | ret.append("%sM" % abs(tdt.months)) | |
113 | usecs = abs( | |
114 | (tdt.days * 24 * 60 * 60 + tdt.seconds) * 1000000 + tdt.microseconds | |
115 | ) | |
143 | 116 | seconds, usecs = divmod(usecs, 1000000) |
144 | 117 | minutes, seconds = divmod(seconds, 60) |
145 | 118 | hours, minutes = divmod(minutes, 60) |
146 | 119 | days, hours = divmod(hours, 24) |
147 | 120 | if days: |
148 | ret.append('%sD' % days) | |
121 | ret.append("%sD" % days) | |
149 | 122 | if hours or minutes or seconds or usecs: |
150 | ret.append('T') | |
123 | ret.append("T") | |
151 | 124 | if hours: |
152 | ret.append('%sH' % hours) | |
125 | ret.append("%sH" % hours) | |
153 | 126 | if minutes: |
154 | ret.append('%sM' % minutes) | |
127 | ret.append("%sM" % minutes) | |
155 | 128 | if seconds or usecs: |
156 | 129 | if usecs: |
157 | ret.append(("%d.%06d" % (seconds, usecs)).rstrip('0')) | |
130 | ret.append(("%d.%06d" % (seconds, usecs)).rstrip("0")) | |
158 | 131 | else: |
159 | 132 | ret.append("%d" % seconds) |
160 | ret.append('S') | |
133 | ret.append("S") | |
161 | 134 | # at least one component has to be there. |
162 | return ret and ''.join(ret) or '0D' | |
163 | elif match.group(0) == '%p': | |
164 | return str(abs(tdt.days // 7)) + 'W' | |
135 | return ret and "".join(ret) or "0D" | |
136 | elif match.group(0) == "%p": | |
137 | return str(abs(tdt.days // 7)) + "W" | |
165 | 138 | return match.group(0) |
166 | return re.sub('%d|%f|%H|%m|%M|%S|%W|%Y|%C|%%|%P|%p', repl, | |
167 | format) | |
139 | ||
140 | return re.sub("%d|%f|%H|%m|%M|%S|%W|%Y|%C|%%|%P|%p", repl, format) | |
168 | 141 | |
169 | 142 | |
170 | 143 | def _strfdt(tdt, format, yeardigits=4): |
171 | ''' | |
144 | """ | |
172 | 145 | this is the work method for time and date instances. |
173 | 146 | |
174 | 147 | see strftime for more details. |
175 | ''' | |
148 | """ | |
149 | ||
176 | 150 | def repl(match): |
177 | ''' | |
151 | """ | |
178 | 152 | lookup format command and return corresponding replacement. |
179 | ''' | |
153 | """ | |
180 | 154 | if match.group(0) in STRF_DT_MAP: |
181 | 155 | return STRF_DT_MAP[match.group(0)](tdt, yeardigits) |
182 | 156 | return match.group(0) |
183 | return re.sub('%d|%f|%H|%j|%m|%M|%S|%w|%W|%Y|%C|%z|%Z|%h|%%', repl, | |
184 | format) | |
157 | ||
158 | return re.sub("%d|%f|%H|%j|%m|%M|%S|%w|%W|%Y|%C|%z|%Z|%h|%%", repl, format) | |
185 | 159 | |
186 | 160 | |
187 | 161 | def strftime(tdt, format, yeardigits=4): |
188 | '''Directive Meaning Notes | |
162 | """Directive Meaning Notes | |
189 | 163 | %d Day of the month as a decimal number [01,31]. |
190 | 164 | %f Microsecond as a decimal number [0,999999], zero-padded |
191 | 165 | on the left (1) |
207 | 181 | %p ISO8601 duration format in weeks. |
208 | 182 | %% A literal '%' character. |
209 | 183 | |
210 | ''' | |
184 | """ | |
211 | 185 | if isinstance(tdt, (timedelta, Duration)): |
212 | 186 | return _strfduration(tdt, format, yeardigits) |
213 | 187 | return _strfdt(tdt, format, yeardigits) |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | This modules provides a method to parse an ISO 8601:2004 time string to a |
28 | 2 | Python datetime.time instance. |
29 | 3 | |
30 | 4 | It supports all basic and extended formats including time zone specifications |
31 | 5 | as described in the ISO standard. |
32 | ''' | |
6 | """ | |
33 | 7 | import re |
34 | from decimal import Decimal | |
8 | from decimal import Decimal, ROUND_FLOOR | |
35 | 9 | from datetime import time |
36 | 10 | |
37 | 11 | from isodate.isostrf import strftime, TIME_EXT_COMPLETE, TZ_EXT |
43 | 17 | |
44 | 18 | |
45 | 19 | def build_time_regexps(): |
46 | ''' | |
20 | """ | |
47 | 21 | Build regular expressions to parse ISO time string. |
48 | 22 | |
49 | 23 | The regular expressions are compiled and stored in TIME_REGEX_CACHE |
50 | 24 | for later reuse. |
51 | ''' | |
25 | """ | |
52 | 26 | if not TIME_REGEX_CACHE: |
53 | 27 | # ISO 8601 time representations allow decimal fractions on least |
54 | 28 | # significant time component. Command and Full Stop are both valid |
66 | 40 | # +-hhmm |
67 | 41 | # +-hh => |
68 | 42 | # isotzinfo.TZ_REGEX |
43 | def add_re(regex_text): | |
44 | TIME_REGEX_CACHE.append(re.compile(r"\A" + regex_text + TZ_REGEX + r"\Z")) | |
45 | ||
69 | 46 | # 1. complete time: |
70 | 47 | # hh:mm:ss.ss ... extended format |
71 | TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2}):" | |
72 | r"(?P<minute>[0-9]{2}):" | |
73 | r"(?P<second>[0-9]{2}" | |
74 | r"([,.][0-9]+)?)" + TZ_REGEX)) | |
48 | add_re( | |
49 | r"T?(?P<hour>[0-9]{2}):" | |
50 | r"(?P<minute>[0-9]{2}):" | |
51 | r"(?P<second>[0-9]{2}" | |
52 | r"([,.][0-9]+)?)" | |
53 | ) | |
75 | 54 | # hhmmss.ss ... basic format |
76 | TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2})" | |
77 | r"(?P<minute>[0-9]{2})" | |
78 | r"(?P<second>[0-9]{2}" | |
79 | r"([,.][0-9]+)?)" + TZ_REGEX)) | |
55 | add_re( | |
56 | r"T?(?P<hour>[0-9]{2})" | |
57 | r"(?P<minute>[0-9]{2})" | |
58 | r"(?P<second>[0-9]{2}" | |
59 | r"([,.][0-9]+)?)" | |
60 | ) | |
80 | 61 | # 2. reduced accuracy: |
81 | 62 | # hh:mm.mm ... extended format |
82 | TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2}):" | |
83 | r"(?P<minute>[0-9]{2}" | |
84 | r"([,.][0-9]+)?)" + TZ_REGEX)) | |
63 | add_re(r"T?(?P<hour>[0-9]{2}):" r"(?P<minute>[0-9]{2}" r"([,.][0-9]+)?)") | |
85 | 64 | # hhmm.mm ... basic format |
86 | TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2})" | |
87 | r"(?P<minute>[0-9]{2}" | |
88 | r"([,.][0-9]+)?)" + TZ_REGEX)) | |
65 | add_re(r"T?(?P<hour>[0-9]{2})" r"(?P<minute>[0-9]{2}" r"([,.][0-9]+)?)") | |
89 | 66 | # hh.hh ... basic format |
90 | TIME_REGEX_CACHE.append(re.compile(r"T?(?P<hour>[0-9]{2}" | |
91 | r"([,.][0-9]+)?)" + TZ_REGEX)) | |
67 | add_re(r"T?(?P<hour>[0-9]{2}" r"([,.][0-9]+)?)") | |
92 | 68 | return TIME_REGEX_CACHE |
93 | 69 | |
94 | 70 | |
95 | 71 | def parse_time(timestring): |
96 | ''' | |
72 | """ | |
97 | 73 | Parses ISO 8601 times into datetime.time objects. |
98 | 74 | |
99 | 75 | Following ISO 8601 formats are supported: |
109 | 85 | +-hhmm basic hours and minutes |
110 | 86 | +-hh:mm extended hours and minutes |
111 | 87 | +-hh hours |
112 | ''' | |
88 | """ | |
113 | 89 | isotimes = build_time_regexps() |
114 | 90 | for pattern in isotimes: |
115 | 91 | match = pattern.match(timestring) |
117 | 93 | groups = match.groupdict() |
118 | 94 | for key, value in groups.items(): |
119 | 95 | if value is not None: |
120 | groups[key] = value.replace(',', '.') | |
121 | tzinfo = build_tzinfo(groups['tzname'], groups['tzsign'], | |
122 | int(groups['tzhour'] or 0), | |
123 | int(groups['tzmin'] or 0)) | |
124 | if 'second' in groups: | |
125 | # round to microseconds if fractional seconds are more precise | |
126 | second = Decimal(groups['second']).quantize(Decimal('.000001')) | |
96 | groups[key] = value.replace(",", ".") | |
97 | tzinfo = build_tzinfo( | |
98 | groups["tzname"], | |
99 | groups["tzsign"], | |
100 | int(groups["tzhour"] or 0), | |
101 | int(groups["tzmin"] or 0), | |
102 | ) | |
103 | if "second" in groups: | |
104 | second = Decimal(groups["second"]).quantize( | |
105 | Decimal(".000001"), rounding=ROUND_FLOOR | |
106 | ) | |
127 | 107 | microsecond = (second - int(second)) * int(1e6) |
128 | 108 | # int(...) ... no rounding |
129 | 109 | # to_integral() ... rounding |
130 | return time(int(groups['hour']), int(groups['minute']), | |
131 | int(second), int(microsecond.to_integral()), | |
132 | tzinfo) | |
133 | if 'minute' in groups: | |
134 | minute = Decimal(groups['minute']) | |
135 | second = (minute - int(minute)) * 60 | |
110 | return time( | |
111 | int(groups["hour"]), | |
112 | int(groups["minute"]), | |
113 | int(second), | |
114 | int(microsecond.to_integral()), | |
115 | tzinfo, | |
116 | ) | |
117 | if "minute" in groups: | |
118 | minute = Decimal(groups["minute"]) | |
119 | second = Decimal((minute - int(minute)) * 60).quantize( | |
120 | Decimal(".000001"), rounding=ROUND_FLOOR | |
121 | ) | |
136 | 122 | microsecond = (second - int(second)) * int(1e6) |
137 | return time(int(groups['hour']), int(minute), int(second), | |
138 | int(microsecond.to_integral()), tzinfo) | |
123 | return time( | |
124 | int(groups["hour"]), | |
125 | int(minute), | |
126 | int(second), | |
127 | int(microsecond.to_integral()), | |
128 | tzinfo, | |
129 | ) | |
139 | 130 | else: |
140 | 131 | microsecond, second, minute = 0, 0, 0 |
141 | hour = Decimal(groups['hour']) | |
132 | hour = Decimal(groups["hour"]) | |
142 | 133 | minute = (hour - int(hour)) * 60 |
143 | 134 | second = (minute - int(minute)) * 60 |
144 | 135 | microsecond = (second - int(second)) * int(1e6) |
145 | return time(int(hour), int(minute), int(second), | |
146 | int(microsecond.to_integral()), tzinfo) | |
147 | raise ISO8601Error('Unrecognised ISO 8601 time format: %r' % timestring) | |
136 | return time( | |
137 | int(hour), | |
138 | int(minute), | |
139 | int(second), | |
140 | int(microsecond.to_integral()), | |
141 | tzinfo, | |
142 | ) | |
143 | raise ISO8601Error("Unrecognised ISO 8601 time format: %r" % timestring) | |
148 | 144 | |
149 | 145 | |
150 | 146 | def time_isoformat(ttime, format=TIME_EXT_COMPLETE + TZ_EXT): |
151 | ''' | |
147 | """ | |
152 | 148 | Format time strings. |
153 | 149 | |
154 | 150 | This method is just a wrapper around isodate.isostrf.strftime and uses |
155 | 151 | Time-Extended-Complete with extended time zone as default format. |
156 | ''' | |
152 | """ | |
157 | 153 | return strftime(ttime, format) |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | This module provides an ISO 8601:2004 time zone info parser. |
28 | 2 | |
29 | 3 | It offers a function to parse the time zone offset as specified by ISO 8601. |
30 | ''' | |
4 | """ | |
31 | 5 | import re |
32 | 6 | |
33 | 7 | from isodate.isoerror import ISO8601Error |
34 | 8 | from isodate.tzinfo import UTC, FixedOffset, ZERO |
35 | 9 | |
36 | TZ_REGEX = r"(?P<tzname>(Z|(?P<tzsign>[+-])"\ | |
37 | r"(?P<tzhour>[0-9]{2})(:?(?P<tzmin>[0-9]{2}))?)?)" | |
10 | TZ_REGEX = ( | |
11 | r"(?P<tzname>(Z|(?P<tzsign>[+-])" r"(?P<tzhour>[0-9]{2})(:?(?P<tzmin>[0-9]{2}))?)?)" | |
12 | ) | |
38 | 13 | |
39 | 14 | TZ_RE = re.compile(TZ_REGEX) |
40 | 15 | |
41 | 16 | |
42 | def build_tzinfo(tzname, tzsign='+', tzhour=0, tzmin=0): | |
43 | ''' | |
17 | def build_tzinfo(tzname, tzsign="+", tzhour=0, tzmin=0): | |
18 | """ | |
44 | 19 | create a tzinfo instance according to given parameters. |
45 | 20 | |
46 | 21 | tzname: |
47 | 22 | 'Z' ... return UTC |
48 | 23 | '' | None ... return None |
49 | 24 | other ... return FixedOffset |
50 | ''' | |
51 | if tzname is None or tzname == '': | |
25 | """ | |
26 | if tzname is None or tzname == "": | |
52 | 27 | return None |
53 | if tzname == 'Z': | |
28 | if tzname == "Z": | |
54 | 29 | return UTC |
55 | tzsign = ((tzsign == '-') and -1) or 1 | |
30 | tzsign = ((tzsign == "-") and -1) or 1 | |
56 | 31 | return FixedOffset(tzsign * tzhour, tzsign * tzmin, tzname) |
57 | 32 | |
58 | 33 | |
59 | 34 | def parse_tzinfo(tzstring): |
60 | ''' | |
35 | """ | |
61 | 36 | Parses ISO 8601 time zone designators to tzinfo objecs. |
62 | 37 | |
63 | 38 | A time zone designator can be in the following format: |
66 | 41 | +-hhmm basic hours and minutes |
67 | 42 | +-hh:mm extended hours and minutes |
68 | 43 | +-hh hours |
69 | ''' | |
44 | """ | |
70 | 45 | match = TZ_RE.match(tzstring) |
71 | 46 | if match: |
72 | 47 | groups = match.groupdict() |
73 | return build_tzinfo(groups['tzname'], groups['tzsign'], | |
74 | int(groups['tzhour'] or 0), | |
75 | int(groups['tzmin'] or 0)) | |
76 | raise ISO8601Error('%s not a valid time zone info' % tzstring) | |
48 | return build_tzinfo( | |
49 | groups["tzname"], | |
50 | groups["tzsign"], | |
51 | int(groups["tzhour"] or 0), | |
52 | int(groups["tzmin"] or 0), | |
53 | ) | |
54 | raise ISO8601Error("%s not a valid time zone info" % tzstring) | |
77 | 55 | |
78 | 56 | |
79 | def tz_isoformat(dt, format='%Z'): | |
80 | ''' | |
57 | def tz_isoformat(dt, format="%Z"): | |
58 | """ | |
81 | 59 | return time zone offset ISO 8601 formatted. |
82 | 60 | The various ISO formats can be chosen with the format parameter. |
83 | 61 | |
88 | 66 | %h ... +-HH |
89 | 67 | %z ... +-HHMM |
90 | 68 | %Z ... +-HH:MM |
91 | ''' | |
69 | """ | |
92 | 70 | tzinfo = dt.tzinfo |
93 | 71 | if (tzinfo is None) or (tzinfo.utcoffset(dt) is None): |
94 | return '' | |
72 | return "" | |
95 | 73 | if tzinfo.utcoffset(dt) == ZERO and tzinfo.dst(dt) == ZERO: |
96 | return 'Z' | |
74 | return "Z" | |
97 | 75 | tdelta = tzinfo.utcoffset(dt) |
98 | 76 | seconds = tdelta.days * 24 * 60 * 60 + tdelta.seconds |
99 | sign = ((seconds < 0) and '-') or '+' | |
77 | sign = ((seconds < 0) and "-") or "+" | |
100 | 78 | seconds = abs(seconds) |
101 | 79 | minutes, seconds = divmod(seconds, 60) |
102 | 80 | hours, minutes = divmod(minutes, 60) |
103 | 81 | if hours > 99: |
104 | raise OverflowError('can not handle differences > 99 hours') | |
105 | if format == '%Z': | |
106 | return '%s%02d:%02d' % (sign, hours, minutes) | |
107 | elif format == '%z': | |
108 | return '%s%02d%02d' % (sign, hours, minutes) | |
109 | elif format == '%h': | |
110 | return '%s%02d' % (sign, hours) | |
82 | raise OverflowError("can not handle differences > 99 hours") | |
83 | if format == "%Z": | |
84 | return "%s%02d:%02d" % (sign, hours, minutes) | |
85 | elif format == "%z": | |
86 | return "%s%02d%02d" % (sign, hours, minutes) | |
87 | elif format == "%h": | |
88 | return "%s%02d" % (sign, hours) | |
111 | 89 | raise ValueError('unknown format string "%s"' % format) |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | Collect all test suites into one TestSuite instance. |
28 | ''' | |
2 | """ | |
29 | 3 | |
30 | 4 | import unittest |
31 | from isodate.tests import (test_date, test_time, test_datetime, test_duration, | |
32 | test_strf, test_pickle) | |
5 | import warnings | |
6 | from isodate.tests import ( | |
7 | test_date, | |
8 | test_time, | |
9 | test_datetime, | |
10 | test_duration, | |
11 | test_strf, | |
12 | test_pickle, | |
13 | ) | |
33 | 14 | |
34 | 15 | |
35 | 16 | def test_suite(): |
36 | ''' | |
17 | """ | |
37 | 18 | Return a new TestSuite instance consisting of all available TestSuites. |
38 | ''' | |
39 | return unittest.TestSuite([ | |
40 | test_date.test_suite(), | |
41 | test_time.test_suite(), | |
42 | test_datetime.test_suite(), | |
43 | test_duration.test_suite(), | |
44 | test_strf.test_suite(), | |
45 | test_pickle.test_suite(), | |
46 | ]) | |
19 | """ | |
20 | warnings.filterwarnings("error", module=r"isodate(\..)*") | |
21 | ||
22 | return unittest.TestSuite( | |
23 | [ | |
24 | test_date.test_suite(), | |
25 | test_time.test_suite(), | |
26 | test_datetime.test_suite(), | |
27 | test_duration.test_suite(), | |
28 | test_strf.test_suite(), | |
29 | test_pickle.test_suite(), | |
30 | ] | |
31 | ) | |
47 | 32 | |
48 | 33 | |
49 | if __name__ == '__main__': | |
50 | unittest.main(defaultTest='test_suite') | |
34 | if __name__ == "__main__": | |
35 | unittest.main(defaultTest="test_suite") |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | Test cases for the isodate module. |
28 | ''' | |
2 | """ | |
29 | 3 | import unittest |
30 | 4 | from datetime import date |
31 | 5 | from isodate import parse_date, ISO8601Error, date_isoformat |
40 | 14 | # result from the parse_date method. A result of None means an ISO8601Error |
41 | 15 | # is expected. The test cases are grouped into dates with 4 digit years |
42 | 16 | # and 6 digit years. |
43 | TEST_CASES = {4: [('19', date(1901, 1, 1), DATE_CENTURY), | |
44 | ('1985', date(1985, 1, 1), DATE_YEAR), | |
45 | ('1985-04', date(1985, 4, 1), DATE_EXT_MONTH), | |
46 | ('198504', date(1985, 4, 1), DATE_BAS_MONTH), | |
47 | ('1985-04-12', date(1985, 4, 12), DATE_EXT_COMPLETE), | |
48 | ('19850412', date(1985, 4, 12), DATE_BAS_COMPLETE), | |
49 | ('1985102', date(1985, 4, 12), DATE_BAS_ORD_COMPLETE), | |
50 | ('1985-102', date(1985, 4, 12), DATE_EXT_ORD_COMPLETE), | |
51 | ('1985W155', date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE), | |
52 | ('1985-W15-5', date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE), | |
53 | ('1985W15', date(1985, 4, 8), DATE_BAS_WEEK), | |
54 | ('1985-W15', date(1985, 4, 8), DATE_EXT_WEEK), | |
55 | ('1989-W15', date(1989, 4, 10), DATE_EXT_WEEK), | |
56 | ('1989-W15-5', date(1989, 4, 14), DATE_EXT_WEEK_COMPLETE), | |
57 | ('1-W1-1', None, DATE_BAS_WEEK_COMPLETE)], | |
58 | 6: [('+0019', date(1901, 1, 1), DATE_CENTURY), | |
59 | ('+001985', date(1985, 1, 1), DATE_YEAR), | |
60 | ('+001985-04', date(1985, 4, 1), DATE_EXT_MONTH), | |
61 | ('+001985-04-12', date(1985, 4, 12), DATE_EXT_COMPLETE), | |
62 | ('+0019850412', date(1985, 4, 12), DATE_BAS_COMPLETE), | |
63 | ('+001985102', date(1985, 4, 12), DATE_BAS_ORD_COMPLETE), | |
64 | ('+001985-102', date(1985, 4, 12), DATE_EXT_ORD_COMPLETE), | |
65 | ('+001985W155', date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE), | |
66 | ('+001985-W15-5', date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE), | |
67 | ('+001985W15', date(1985, 4, 8), DATE_BAS_WEEK), | |
68 | ('+001985-W15', date(1985, 4, 8), DATE_EXT_WEEK)]} | |
17 | TEST_CASES = { | |
18 | 4: [ | |
19 | ("19", date(1901, 1, 1), DATE_CENTURY), | |
20 | ("1985", date(1985, 1, 1), DATE_YEAR), | |
21 | ("1985-04", date(1985, 4, 1), DATE_EXT_MONTH), | |
22 | ("198504", date(1985, 4, 1), DATE_BAS_MONTH), | |
23 | ("1985-04-12", date(1985, 4, 12), DATE_EXT_COMPLETE), | |
24 | ("19850412", date(1985, 4, 12), DATE_BAS_COMPLETE), | |
25 | ("1985102", date(1985, 4, 12), DATE_BAS_ORD_COMPLETE), | |
26 | ("1985-102", date(1985, 4, 12), DATE_EXT_ORD_COMPLETE), | |
27 | ("1985W155", date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE), | |
28 | ("1985-W15-5", date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE), | |
29 | ("1985W15", date(1985, 4, 8), DATE_BAS_WEEK), | |
30 | ("1985-W15", date(1985, 4, 8), DATE_EXT_WEEK), | |
31 | ("1989-W15", date(1989, 4, 10), DATE_EXT_WEEK), | |
32 | ("1989-W15-5", date(1989, 4, 14), DATE_EXT_WEEK_COMPLETE), | |
33 | ("1-W1-1", None, DATE_BAS_WEEK_COMPLETE), | |
34 | ], | |
35 | 6: [ | |
36 | ("+0019", date(1901, 1, 1), DATE_CENTURY), | |
37 | ("+001985", date(1985, 1, 1), DATE_YEAR), | |
38 | ("+001985-04", date(1985, 4, 1), DATE_EXT_MONTH), | |
39 | ("+001985-04-12", date(1985, 4, 12), DATE_EXT_COMPLETE), | |
40 | ("+0019850412", date(1985, 4, 12), DATE_BAS_COMPLETE), | |
41 | ("+001985102", date(1985, 4, 12), DATE_BAS_ORD_COMPLETE), | |
42 | ("+001985-102", date(1985, 4, 12), DATE_EXT_ORD_COMPLETE), | |
43 | ("+001985W155", date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE), | |
44 | ("+001985-W15-5", date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE), | |
45 | ("+001985W15", date(1985, 4, 8), DATE_BAS_WEEK), | |
46 | ("+001985-W15", date(1985, 4, 8), DATE_EXT_WEEK), | |
47 | ], | |
48 | } | |
69 | 49 | |
70 | 50 | |
71 | 51 | def create_testcase(yeardigits, datestring, expectation, format): |
72 | ''' | |
52 | """ | |
73 | 53 | Create a TestCase class for a specific test. |
74 | 54 | |
75 | 55 | This allows having a separate TestCase for each test tuple from the |
76 | 56 | TEST_CASES list, so that a failed test won't stop other tests. |
77 | ''' | |
57 | """ | |
78 | 58 | |
79 | 59 | class TestDate(unittest.TestCase): |
80 | ''' | |
60 | """ | |
81 | 61 | A test case template to parse an ISO date string into a date |
82 | 62 | object. |
83 | ''' | |
63 | """ | |
84 | 64 | |
85 | 65 | def test_parse(self): |
86 | ''' | |
66 | """ | |
87 | 67 | Parse an ISO date string and compare it to the expected value. |
88 | ''' | |
68 | """ | |
89 | 69 | if expectation is None: |
90 | self.assertRaises(ISO8601Error, parse_date, datestring, | |
91 | yeardigits) | |
70 | self.assertRaises(ISO8601Error, parse_date, datestring, yeardigits) | |
92 | 71 | else: |
93 | 72 | result = parse_date(datestring, yeardigits) |
94 | 73 | self.assertEqual(result, expectation) |
95 | 74 | |
96 | 75 | def test_format(self): |
97 | ''' | |
76 | """ | |
98 | 77 | Take date object and create ISO string from it. |
99 | 78 | This is the reverse test to test_parse. |
100 | ''' | |
79 | """ | |
101 | 80 | if expectation is None: |
102 | self.assertRaises(AttributeError, | |
103 | date_isoformat, expectation, format, | |
104 | yeardigits) | |
81 | self.assertRaises( | |
82 | AttributeError, date_isoformat, expectation, format, yeardigits | |
83 | ) | |
105 | 84 | else: |
106 | self.assertEqual(date_isoformat(expectation, format, | |
107 | yeardigits), | |
108 | datestring) | |
85 | self.assertEqual( | |
86 | date_isoformat(expectation, format, yeardigits), datestring | |
87 | ) | |
109 | 88 | |
110 | 89 | return unittest.TestLoader().loadTestsFromTestCase(TestDate) |
111 | 90 | |
112 | 91 | |
113 | 92 | def test_suite(): |
114 | ''' | |
93 | """ | |
115 | 94 | Construct a TestSuite instance for all test cases. |
116 | ''' | |
95 | """ | |
117 | 96 | suite = unittest.TestSuite() |
118 | 97 | for yeardigits, tests in TEST_CASES.items(): |
119 | 98 | for datestring, expectation, format in tests: |
120 | suite.addTest(create_testcase(yeardigits, datestring, | |
121 | expectation, format)) | |
99 | suite.addTest(create_testcase(yeardigits, datestring, expectation, format)) | |
122 | 100 | return suite |
123 | 101 | |
124 | 102 | |
127 | 105 | return test_suite() |
128 | 106 | |
129 | 107 | |
130 | if __name__ == '__main__': | |
131 | unittest.main(defaultTest='test_suite') | |
108 | if __name__ == "__main__": | |
109 | unittest.main(defaultTest="test_suite") |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | Test cases for the isodatetime module. |
28 | ''' | |
2 | """ | |
29 | 3 | import unittest |
30 | 4 | from datetime import datetime |
31 | 5 | |
40 | 14 | # the following list contains tuples of ISO datetime strings and the expected |
41 | 15 | # result from the parse_datetime method. A result of None means an ISO8601Error |
42 | 16 | # is expected. |
43 | TEST_CASES = [('19850412T1015', datetime(1985, 4, 12, 10, 15), | |
44 | DATE_BAS_COMPLETE + 'T' + TIME_BAS_MINUTE, | |
45 | '19850412T1015'), | |
46 | ('1985-04-12T10:15', datetime(1985, 4, 12, 10, 15), | |
47 | DATE_EXT_COMPLETE + 'T' + TIME_EXT_MINUTE, | |
48 | '1985-04-12T10:15'), | |
49 | ('1985102T1015Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC), | |
50 | DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS, | |
51 | '1985102T1015Z'), | |
52 | ('1985-102T10:15Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC), | |
53 | DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_EXT, | |
54 | '1985-102T10:15Z'), | |
55 | ('1985W155T1015+0400', datetime(1985, 4, 12, 10, 15, | |
56 | tzinfo=FixedOffset(4, 0, | |
57 | '+0400')), | |
58 | DATE_BAS_WEEK_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS, | |
59 | '1985W155T1015+0400'), | |
60 | ('1985-W15-5T10:15+04', datetime(1985, 4, 12, 10, 15, | |
61 | tzinfo=FixedOffset(4, 0, | |
62 | '+0400'),), | |
63 | DATE_EXT_WEEK_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_HOUR, | |
64 | '1985-W15-5T10:15+04'), | |
65 | ('1985-W15-5T10:15-0430', | |
66 | datetime(1985, 4, 12, 10, 15, tzinfo=FixedOffset(-4, -30, | |
67 | '-0430'),), | |
68 | DATE_EXT_WEEK_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_BAS, | |
69 | '1985-W15-5T10:15-0430'), | |
70 | ('1985-W15-5T10:15+04:45', | |
71 | datetime(1985, 4, 12, 10, 15, tzinfo=FixedOffset(4, 45, | |
72 | '+04:45'),), | |
73 | DATE_EXT_WEEK_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_EXT, | |
74 | '1985-W15-5T10:15+04:45'), | |
75 | ('20110410T101225.123000Z', | |
76 | datetime(2011, 4, 10, 10, 12, 25, 123000, tzinfo=UTC), | |
77 | DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE + ".%f" + TZ_BAS, | |
78 | '20110410T101225.123000Z'), | |
79 | ('2012-10-12T08:29:46.069178Z', | |
80 | datetime(2012, 10, 12, 8, 29, 46, 69178, tzinfo=UTC), | |
81 | DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS, | |
82 | '2012-10-12T08:29:46.069178Z'), | |
83 | ('2012-10-12T08:29:46.691780Z', | |
84 | datetime(2012, 10, 12, 8, 29, 46, 691780, tzinfo=UTC), | |
85 | DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS, | |
86 | '2012-10-12T08:29:46.691780Z'), | |
87 | ('2012-10-30T08:55:22.1234567Z', | |
88 | datetime(2012, 10, 30, 8, 55, 22, 123457, tzinfo=UTC), | |
89 | DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS, | |
90 | '2012-10-30T08:55:22.123457Z'), | |
91 | ('2012-10-30T08:55:22.1234561Z', | |
92 | datetime(2012, 10, 30, 8, 55, 22, 123456, tzinfo=UTC), | |
93 | DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS, | |
94 | '2012-10-30T08:55:22.123456Z'), | |
95 | ('2014-08-18 14:55:22.123456Z', None, | |
96 | DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS, | |
97 | '2014-08-18T14:55:22.123456Z'), | |
98 | ] | |
17 | TEST_CASES = [ | |
18 | ( | |
19 | "19850412T1015", | |
20 | datetime(1985, 4, 12, 10, 15), | |
21 | DATE_BAS_COMPLETE + "T" + TIME_BAS_MINUTE, | |
22 | "19850412T1015", | |
23 | ), | |
24 | ( | |
25 | "1985-04-12T10:15", | |
26 | datetime(1985, 4, 12, 10, 15), | |
27 | DATE_EXT_COMPLETE + "T" + TIME_EXT_MINUTE, | |
28 | "1985-04-12T10:15", | |
29 | ), | |
30 | ( | |
31 | "1985102T1015Z", | |
32 | datetime(1985, 4, 12, 10, 15, tzinfo=UTC), | |
33 | DATE_BAS_ORD_COMPLETE + "T" + TIME_BAS_MINUTE + TZ_BAS, | |
34 | "1985102T1015Z", | |
35 | ), | |
36 | ( | |
37 | "1985-102T10:15Z", | |
38 | datetime(1985, 4, 12, 10, 15, tzinfo=UTC), | |
39 | DATE_EXT_ORD_COMPLETE + "T" + TIME_EXT_MINUTE + TZ_EXT, | |
40 | "1985-102T10:15Z", | |
41 | ), | |
42 | ( | |
43 | "1985W155T1015+0400", | |
44 | datetime(1985, 4, 12, 10, 15, tzinfo=FixedOffset(4, 0, "+0400")), | |
45 | DATE_BAS_WEEK_COMPLETE + "T" + TIME_BAS_MINUTE + TZ_BAS, | |
46 | "1985W155T1015+0400", | |
47 | ), | |
48 | ( | |
49 | "1985-W15-5T10:15+04", | |
50 | datetime( | |
51 | 1985, | |
52 | 4, | |
53 | 12, | |
54 | 10, | |
55 | 15, | |
56 | tzinfo=FixedOffset(4, 0, "+0400"), | |
57 | ), | |
58 | DATE_EXT_WEEK_COMPLETE + "T" + TIME_EXT_MINUTE + TZ_HOUR, | |
59 | "1985-W15-5T10:15+04", | |
60 | ), | |
61 | ( | |
62 | "1985-W15-5T10:15-0430", | |
63 | datetime( | |
64 | 1985, | |
65 | 4, | |
66 | 12, | |
67 | 10, | |
68 | 15, | |
69 | tzinfo=FixedOffset(-4, -30, "-0430"), | |
70 | ), | |
71 | DATE_EXT_WEEK_COMPLETE + "T" + TIME_EXT_MINUTE + TZ_BAS, | |
72 | "1985-W15-5T10:15-0430", | |
73 | ), | |
74 | ( | |
75 | "1985-W15-5T10:15+04:45", | |
76 | datetime( | |
77 | 1985, | |
78 | 4, | |
79 | 12, | |
80 | 10, | |
81 | 15, | |
82 | tzinfo=FixedOffset(4, 45, "+04:45"), | |
83 | ), | |
84 | DATE_EXT_WEEK_COMPLETE + "T" + TIME_EXT_MINUTE + TZ_EXT, | |
85 | "1985-W15-5T10:15+04:45", | |
86 | ), | |
87 | ( | |
88 | "20110410T101225.123000Z", | |
89 | datetime(2011, 4, 10, 10, 12, 25, 123000, tzinfo=UTC), | |
90 | DATE_BAS_COMPLETE + "T" + TIME_BAS_COMPLETE + ".%f" + TZ_BAS, | |
91 | "20110410T101225.123000Z", | |
92 | ), | |
93 | ( | |
94 | "2012-10-12T08:29:46.069178Z", | |
95 | datetime(2012, 10, 12, 8, 29, 46, 69178, tzinfo=UTC), | |
96 | DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + ".%f" + TZ_BAS, | |
97 | "2012-10-12T08:29:46.069178Z", | |
98 | ), | |
99 | ( | |
100 | "2012-10-12T08:29:46.691780Z", | |
101 | datetime(2012, 10, 12, 8, 29, 46, 691780, tzinfo=UTC), | |
102 | DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + ".%f" + TZ_BAS, | |
103 | "2012-10-12T08:29:46.691780Z", | |
104 | ), | |
105 | ( | |
106 | "2012-10-30T08:55:22.1234567Z", | |
107 | datetime(2012, 10, 30, 8, 55, 22, 123456, tzinfo=UTC), | |
108 | DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + ".%f" + TZ_BAS, | |
109 | "2012-10-30T08:55:22.123456Z", | |
110 | ), | |
111 | ( | |
112 | "2012-10-30T08:55:22.1234561Z", | |
113 | datetime(2012, 10, 30, 8, 55, 22, 123456, tzinfo=UTC), | |
114 | DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + ".%f" + TZ_BAS, | |
115 | "2012-10-30T08:55:22.123456Z", | |
116 | ), | |
117 | ( | |
118 | "2014-08-18 14:55:22.123456Z", | |
119 | None, | |
120 | DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + ".%f" + TZ_BAS, | |
121 | "2014-08-18T14:55:22.123456Z", | |
122 | ), | |
123 | ] | |
99 | 124 | |
100 | 125 | |
101 | 126 | def create_testcase(datetimestring, expectation, format, output): |
107 | 132 | """ |
108 | 133 | |
109 | 134 | class TestDateTime(unittest.TestCase): |
110 | ''' | |
135 | """ | |
111 | 136 | A test case template to parse an ISO datetime string into a |
112 | 137 | datetime object. |
113 | ''' | |
138 | """ | |
114 | 139 | |
115 | 140 | def test_parse(self): |
116 | ''' | |
141 | """ | |
117 | 142 | Parse an ISO datetime string and compare it to the expected value. |
118 | ''' | |
143 | """ | |
119 | 144 | if expectation is None: |
120 | 145 | self.assertRaises(ISO8601Error, parse_datetime, datetimestring) |
121 | 146 | else: |
122 | 147 | self.assertEqual(parse_datetime(datetimestring), expectation) |
123 | 148 | |
124 | 149 | def test_format(self): |
125 | ''' | |
150 | """ | |
126 | 151 | Take datetime object and create ISO string from it. |
127 | 152 | This is the reverse test to test_parse. |
128 | ''' | |
153 | """ | |
129 | 154 | if expectation is None: |
130 | self.assertRaises(AttributeError, | |
131 | datetime_isoformat, expectation, format) | |
155 | self.assertRaises( | |
156 | AttributeError, datetime_isoformat, expectation, format | |
157 | ) | |
132 | 158 | else: |
133 | self.assertEqual(datetime_isoformat(expectation, format), | |
134 | output) | |
159 | self.assertEqual(datetime_isoformat(expectation, format), output) | |
135 | 160 | |
136 | 161 | return unittest.TestLoader().loadTestsFromTestCase(TestDateTime) |
137 | 162 | |
138 | 163 | |
139 | 164 | def test_suite(): |
140 | ''' | |
165 | """ | |
141 | 166 | Construct a TestSuite instance for all test cases. |
142 | ''' | |
167 | """ | |
143 | 168 | suite = unittest.TestSuite() |
144 | 169 | for datetimestring, expectation, format, output in TEST_CASES: |
145 | suite.addTest(create_testcase(datetimestring, expectation, | |
146 | format, output)) | |
170 | suite.addTest(create_testcase(datetimestring, expectation, format, output)) | |
147 | 171 | return suite |
148 | 172 | |
149 | 173 | |
152 | 176 | return test_suite() |
153 | 177 | |
154 | 178 | |
155 | if __name__ == '__main__': | |
156 | unittest.main(defaultTest='test_suite') | |
179 | if __name__ == "__main__": | |
180 | unittest.main(defaultTest="test_suite") |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | Test cases for the isoduration module. |
28 | ''' | |
2 | """ | |
29 | 3 | import unittest |
30 | 4 | import operator |
31 | 5 | from datetime import timedelta, date, datetime |
36 | 10 | # the following list contains tuples of ISO duration strings and the expected |
37 | 11 | # result from the parse_duration method. A result of None means an ISO8601Error |
38 | 12 | # is expected. |
39 | PARSE_TEST_CASES = {'P18Y9M4DT11H9M8S': (Duration(4, 8, 0, 0, 9, 11, 0, 9, 18), | |
40 | D_DEFAULT, None), | |
41 | 'P2W': (timedelta(weeks=2), D_WEEK, None), | |
42 | 'P3Y6M4DT12H30M5S': (Duration(4, 5, 0, 0, 30, 12, 0, 6, 3), | |
43 | D_DEFAULT, None), | |
44 | 'P23DT23H': (timedelta(hours=23, days=23), | |
45 | D_DEFAULT, None), | |
46 | 'P4Y': (Duration(years=4), D_DEFAULT, None), | |
47 | 'P1M': (Duration(months=1), D_DEFAULT, None), | |
48 | 'PT1M': (timedelta(minutes=1), D_DEFAULT, None), | |
49 | 'P0.5Y': (Duration(years=0.5), D_DEFAULT, None), | |
50 | 'PT36H': (timedelta(hours=36), D_DEFAULT, 'P1DT12H'), | |
51 | 'P1DT12H': (timedelta(days=1, hours=12), D_DEFAULT, None), | |
52 | '+P11D': (timedelta(days=11), D_DEFAULT, 'P11D'), | |
53 | '-P2W': (timedelta(weeks=-2), D_WEEK, None), | |
54 | '-P2.2W': (timedelta(weeks=-2.2), D_DEFAULT, | |
55 | '-P15DT9H36M'), | |
56 | 'P1DT2H3M4S': (timedelta(days=1, hours=2, minutes=3, | |
57 | seconds=4), D_DEFAULT, None), | |
58 | 'P1DT2H3M': (timedelta(days=1, hours=2, minutes=3), | |
59 | D_DEFAULT, None), | |
60 | 'P1DT2H': (timedelta(days=1, hours=2), D_DEFAULT, None), | |
61 | 'PT2H': (timedelta(hours=2), D_DEFAULT, None), | |
62 | 'PT2.3H': (timedelta(hours=2.3), D_DEFAULT, 'PT2H18M'), | |
63 | 'PT2H3M4S': (timedelta(hours=2, minutes=3, seconds=4), | |
64 | D_DEFAULT, None), | |
65 | 'PT3M4S': (timedelta(minutes=3, seconds=4), D_DEFAULT, | |
66 | None), | |
67 | 'PT22S': (timedelta(seconds=22), D_DEFAULT, None), | |
68 | 'PT22.22S': (timedelta(seconds=22.22), 'PT%S.%fS', | |
69 | 'PT22.220000S'), | |
70 | '-P2Y': (Duration(years=-2), D_DEFAULT, None), | |
71 | '-P3Y6M4DT12H30M5S': (Duration(-4, -5, 0, 0, -30, -12, 0, | |
72 | -6, -3), D_DEFAULT, None), | |
73 | '-P1DT2H3M4S': (timedelta(days=-1, hours=-2, minutes=-3, | |
74 | seconds=-4), D_DEFAULT, None), | |
75 | # alternative format | |
76 | 'P0018-09-04T11:09:08': (Duration(4, 8, 0, 0, 9, 11, 0, 9, | |
77 | 18), D_ALT_EXT, None), | |
78 | # 'PT000022.22': timedelta(seconds=22.22), | |
79 | } | |
13 | PARSE_TEST_CASES = { | |
14 | "P18Y9M4DT11H9M8S": (Duration(4, 8, 0, 0, 9, 11, 0, 9, 18), D_DEFAULT, None), | |
15 | "P2W": (timedelta(weeks=2), D_WEEK, None), | |
16 | "P3Y6M4DT12H30M5S": (Duration(4, 5, 0, 0, 30, 12, 0, 6, 3), D_DEFAULT, None), | |
17 | "P23DT23H": (timedelta(hours=23, days=23), D_DEFAULT, None), | |
18 | "P4Y": (Duration(years=4), D_DEFAULT, None), | |
19 | "P1M": (Duration(months=1), D_DEFAULT, None), | |
20 | "PT1M": (timedelta(minutes=1), D_DEFAULT, None), | |
21 | "P0.5Y": (Duration(years=0.5), D_DEFAULT, None), | |
22 | "PT36H": (timedelta(hours=36), D_DEFAULT, "P1DT12H"), | |
23 | "P1DT12H": (timedelta(days=1, hours=12), D_DEFAULT, None), | |
24 | "+P11D": (timedelta(days=11), D_DEFAULT, "P11D"), | |
25 | "-P2W": (timedelta(weeks=-2), D_WEEK, None), | |
26 | "-P2.2W": (timedelta(weeks=-2.2), D_DEFAULT, "-P15DT9H36M"), | |
27 | "P1DT2H3M4S": (timedelta(days=1, hours=2, minutes=3, seconds=4), D_DEFAULT, None), | |
28 | "P1DT2H3M": (timedelta(days=1, hours=2, minutes=3), D_DEFAULT, None), | |
29 | "P1DT2H": (timedelta(days=1, hours=2), D_DEFAULT, None), | |
30 | "PT2H": (timedelta(hours=2), D_DEFAULT, None), | |
31 | "PT2.3H": (timedelta(hours=2.3), D_DEFAULT, "PT2H18M"), | |
32 | "PT2H3M4S": (timedelta(hours=2, minutes=3, seconds=4), D_DEFAULT, None), | |
33 | "PT3M4S": (timedelta(minutes=3, seconds=4), D_DEFAULT, None), | |
34 | "PT22S": (timedelta(seconds=22), D_DEFAULT, None), | |
35 | "PT22.22S": (timedelta(seconds=22.22), "PT%S.%fS", "PT22.220000S"), | |
36 | "-P2Y": (Duration(years=-2), D_DEFAULT, None), | |
37 | "-P3Y6M4DT12H30M5S": (Duration(-4, -5, 0, 0, -30, -12, 0, -6, -3), D_DEFAULT, None), | |
38 | "-P1DT2H3M4S": ( | |
39 | timedelta(days=-1, hours=-2, minutes=-3, seconds=-4), | |
40 | D_DEFAULT, | |
41 | None, | |
42 | ), | |
43 | # alternative format | |
44 | "P0018-09-04T11:09:08": (Duration(4, 8, 0, 0, 9, 11, 0, 9, 18), D_ALT_EXT, None), | |
45 | # 'PT000022.22': timedelta(seconds=22.22), | |
46 | } | |
80 | 47 | |
81 | 48 | # d1 d2 '+', '-', '>' |
82 | 49 | # A list of test cases to test addition and subtraction between datetime and |
84 | 51 | # each tuple contains 2 duration strings, and a result string for addition and |
85 | 52 | # one for subtraction. The last value says, if the first duration is greater |
86 | 53 | # than the second. |
87 | MATH_TEST_CASES = (('P5Y7M1DT9H45M16.72S', 'PT27M24.68S', | |
88 | 'P5Y7M1DT10H12M41.4S', 'P5Y7M1DT9H17M52.04S', None), | |
89 | ('PT28M12.73S', 'PT56M29.92S', | |
90 | 'PT1H24M42.65S', '-PT28M17.19S', False), | |
91 | ('P3Y7M23DT5H25M0.33S', 'PT1H1.95S', | |
92 | 'P3Y7M23DT6H25M2.28S', 'P3Y7M23DT4H24M58.38S', None), | |
93 | ('PT1H1.95S', 'P3Y7M23DT5H25M0.33S', | |
94 | 'P3Y7M23DT6H25M2.28S', '-P3Y7M23DT4H24M58.38S', None), | |
95 | ('P1332DT55M0.33S', 'PT1H1.95S', | |
96 | 'P1332DT1H55M2.28S', 'P1331DT23H54M58.38S', True), | |
97 | ('PT1H1.95S', 'P1332DT55M0.33S', | |
98 | 'P1332DT1H55M2.28S', '-P1331DT23H54M58.38S', False)) | |
54 | MATH_TEST_CASES = ( | |
55 | ( | |
56 | "P5Y7M1DT9H45M16.72S", | |
57 | "PT27M24.68S", | |
58 | "P5Y7M1DT10H12M41.4S", | |
59 | "P5Y7M1DT9H17M52.04S", | |
60 | None, | |
61 | ), | |
62 | ("PT28M12.73S", "PT56M29.92S", "PT1H24M42.65S", "-PT28M17.19S", False), | |
63 | ( | |
64 | "P3Y7M23DT5H25M0.33S", | |
65 | "PT1H1.95S", | |
66 | "P3Y7M23DT6H25M2.28S", | |
67 | "P3Y7M23DT4H24M58.38S", | |
68 | None, | |
69 | ), | |
70 | ( | |
71 | "PT1H1.95S", | |
72 | "P3Y7M23DT5H25M0.33S", | |
73 | "P3Y7M23DT6H25M2.28S", | |
74 | "-P3Y7M23DT4H24M58.38S", | |
75 | None, | |
76 | ), | |
77 | ("P1332DT55M0.33S", "PT1H1.95S", "P1332DT1H55M2.28S", "P1331DT23H54M58.38S", True), | |
78 | ( | |
79 | "PT1H1.95S", | |
80 | "P1332DT55M0.33S", | |
81 | "P1332DT1H55M2.28S", | |
82 | "-P1331DT23H54M58.38S", | |
83 | False, | |
84 | ), | |
85 | ) | |
99 | 86 | |
100 | 87 | |
101 | 88 | # A list of test cases to test addition and subtraction of date/datetime |
102 | 89 | # and Duration objects. They are tested against the results of an |
103 | 90 | # equal long timedelta duration. |
104 | DATE_TEST_CASES = ((date(2008, 2, 29), | |
105 | timedelta(days=10, hours=12, minutes=20), | |
106 | Duration(days=10, hours=12, minutes=20)), | |
107 | (date(2008, 1, 31), | |
108 | timedelta(days=10, hours=12, minutes=20), | |
109 | Duration(days=10, hours=12, minutes=20)), | |
110 | (datetime(2008, 2, 29), | |
111 | timedelta(days=10, hours=12, minutes=20), | |
112 | Duration(days=10, hours=12, minutes=20)), | |
113 | (datetime(2008, 1, 31), | |
114 | timedelta(days=10, hours=12, minutes=20), | |
115 | Duration(days=10, hours=12, minutes=20)), | |
116 | (datetime(2008, 4, 21), | |
117 | timedelta(days=10, hours=12, minutes=20), | |
118 | Duration(days=10, hours=12, minutes=20)), | |
119 | (datetime(2008, 5, 5), | |
120 | timedelta(days=10, hours=12, minutes=20), | |
121 | Duration(days=10, hours=12, minutes=20)), | |
122 | (datetime(2000, 1, 1), | |
123 | timedelta(hours=-33), | |
124 | Duration(hours=-33)), | |
125 | (datetime(2008, 5, 5), | |
126 | Duration(years=1, months=1, days=10, hours=12, | |
127 | minutes=20), | |
128 | Duration(months=13, days=10, hours=12, minutes=20)), | |
129 | (datetime(2000, 3, 30), | |
130 | Duration(years=1, months=1, days=10, hours=12, | |
131 | minutes=20), | |
132 | Duration(months=13, days=10, hours=12, minutes=20)), | |
133 | ) | |
91 | DATE_TEST_CASES = ( | |
92 | ( | |
93 | date(2008, 2, 29), | |
94 | timedelta(days=10, hours=12, minutes=20), | |
95 | Duration(days=10, hours=12, minutes=20), | |
96 | ), | |
97 | ( | |
98 | date(2008, 1, 31), | |
99 | timedelta(days=10, hours=12, minutes=20), | |
100 | Duration(days=10, hours=12, minutes=20), | |
101 | ), | |
102 | ( | |
103 | datetime(2008, 2, 29), | |
104 | timedelta(days=10, hours=12, minutes=20), | |
105 | Duration(days=10, hours=12, minutes=20), | |
106 | ), | |
107 | ( | |
108 | datetime(2008, 1, 31), | |
109 | timedelta(days=10, hours=12, minutes=20), | |
110 | Duration(days=10, hours=12, minutes=20), | |
111 | ), | |
112 | ( | |
113 | datetime(2008, 4, 21), | |
114 | timedelta(days=10, hours=12, minutes=20), | |
115 | Duration(days=10, hours=12, minutes=20), | |
116 | ), | |
117 | ( | |
118 | datetime(2008, 5, 5), | |
119 | timedelta(days=10, hours=12, minutes=20), | |
120 | Duration(days=10, hours=12, minutes=20), | |
121 | ), | |
122 | (datetime(2000, 1, 1), timedelta(hours=-33), Duration(hours=-33)), | |
123 | ( | |
124 | datetime(2008, 5, 5), | |
125 | Duration(years=1, months=1, days=10, hours=12, minutes=20), | |
126 | Duration(months=13, days=10, hours=12, minutes=20), | |
127 | ), | |
128 | ( | |
129 | datetime(2000, 3, 30), | |
130 | Duration(years=1, months=1, days=10, hours=12, minutes=20), | |
131 | Duration(months=13, days=10, hours=12, minutes=20), | |
132 | ), | |
133 | ) | |
134 | 134 | |
135 | 135 | # A list of test cases of additon of date/datetime and Duration. The results |
136 | 136 | # are compared against a given expected result. |
137 | 137 | DATE_CALC_TEST_CASES = ( |
138 | (date(2000, 2, 1), | |
139 | Duration(years=1, months=1), | |
140 | date(2001, 3, 1)), | |
141 | (date(2000, 2, 29), | |
142 | Duration(years=1, months=1), | |
143 | date(2001, 3, 29)), | |
144 | (date(2000, 2, 29), | |
145 | Duration(years=1), | |
146 | date(2001, 2, 28)), | |
147 | (date(1996, 2, 29), | |
148 | Duration(years=4), | |
149 | date(2000, 2, 29)), | |
150 | (date(2096, 2, 29), | |
151 | Duration(years=4), | |
152 | date(2100, 2, 28)), | |
153 | (date(2000, 2, 1), | |
154 | Duration(years=-1, months=-1), | |
155 | date(1999, 1, 1)), | |
156 | (date(2000, 2, 29), | |
157 | Duration(years=-1, months=-1), | |
158 | date(1999, 1, 29)), | |
159 | (date(2000, 2, 1), | |
160 | Duration(years=1, months=1, days=1), | |
161 | date(2001, 3, 2)), | |
162 | (date(2000, 2, 29), | |
163 | Duration(years=1, months=1, days=1), | |
164 | date(2001, 3, 30)), | |
165 | (date(2000, 2, 29), | |
166 | Duration(years=1, days=1), | |
167 | date(2001, 3, 1)), | |
168 | (date(1996, 2, 29), | |
169 | Duration(years=4, days=1), | |
170 | date(2000, 3, 1)), | |
171 | (date(2096, 2, 29), | |
172 | Duration(years=4, days=1), | |
173 | date(2100, 3, 1)), | |
174 | (date(2000, 2, 1), | |
175 | Duration(years=-1, months=-1, days=-1), | |
176 | date(1998, 12, 31)), | |
177 | (date(2000, 2, 29), | |
178 | Duration(years=-1, months=-1, days=-1), | |
179 | date(1999, 1, 28)), | |
180 | (date(2001, 4, 1), | |
181 | Duration(years=-1, months=-1, days=-1), | |
182 | date(2000, 2, 29)), | |
183 | (date(2000, 4, 1), | |
184 | Duration(years=-1, months=-1, days=-1), | |
185 | date(1999, 2, 28)), | |
186 | (Duration(years=1, months=2), | |
187 | Duration(years=0, months=0, days=1), | |
188 | Duration(years=1, months=2, days=1)), | |
189 | (Duration(years=-1, months=-1, days=-1), | |
190 | date(2000, 4, 1), | |
191 | date(1999, 2, 28)), | |
192 | (Duration(years=1, months=1, weeks=5), | |
193 | date(2000, 1, 30), | |
194 | date(2001, 4, 4)), | |
195 | (parse_duration("P1Y1M5W"), | |
196 | date(2000, 1, 30), | |
197 | date(2001, 4, 4)), | |
198 | (parse_duration("P0.5Y"), | |
199 | date(2000, 1, 30), | |
200 | None), | |
201 | (Duration(years=1, months=1, hours=3), | |
202 | datetime(2000, 1, 30, 12, 15, 00), | |
203 | datetime(2001, 2, 28, 15, 15, 00)), | |
204 | (parse_duration("P1Y1MT3H"), | |
205 | datetime(2000, 1, 30, 12, 15, 00), | |
206 | datetime(2001, 2, 28, 15, 15, 00)), | |
207 | (Duration(years=1, months=2), | |
208 | timedelta(days=1), | |
209 | Duration(years=1, months=2, days=1)), | |
210 | (timedelta(days=1), | |
211 | Duration(years=1, months=2), | |
212 | Duration(years=1, months=2, days=1)), | |
213 | (datetime(2008, 1, 1, 0, 2), | |
214 | Duration(months=1), | |
215 | datetime(2008, 2, 1, 0, 2)), | |
216 | (datetime.strptime("200802", "%Y%M"), | |
217 | parse_duration("P1M"), | |
218 | datetime(2008, 2, 1, 0, 2)), | |
219 | (datetime(2008, 2, 1), | |
220 | Duration(months=1), | |
221 | datetime(2008, 3, 1)), | |
222 | (datetime.strptime("200802", "%Y%m"), | |
223 | parse_duration("P1M"), | |
224 | datetime(2008, 3, 1)), | |
138 | (date(2000, 2, 1), Duration(years=1, months=1), date(2001, 3, 1)), | |
139 | (date(2000, 2, 29), Duration(years=1, months=1), date(2001, 3, 29)), | |
140 | (date(2000, 2, 29), Duration(years=1), date(2001, 2, 28)), | |
141 | (date(1996, 2, 29), Duration(years=4), date(2000, 2, 29)), | |
142 | (date(2096, 2, 29), Duration(years=4), date(2100, 2, 28)), | |
143 | (date(2000, 2, 1), Duration(years=-1, months=-1), date(1999, 1, 1)), | |
144 | (date(2000, 2, 29), Duration(years=-1, months=-1), date(1999, 1, 29)), | |
145 | (date(2000, 2, 1), Duration(years=1, months=1, days=1), date(2001, 3, 2)), | |
146 | (date(2000, 2, 29), Duration(years=1, months=1, days=1), date(2001, 3, 30)), | |
147 | (date(2000, 2, 29), Duration(years=1, days=1), date(2001, 3, 1)), | |
148 | (date(1996, 2, 29), Duration(years=4, days=1), date(2000, 3, 1)), | |
149 | (date(2096, 2, 29), Duration(years=4, days=1), date(2100, 3, 1)), | |
150 | (date(2000, 2, 1), Duration(years=-1, months=-1, days=-1), date(1998, 12, 31)), | |
151 | (date(2000, 2, 29), Duration(years=-1, months=-1, days=-1), date(1999, 1, 28)), | |
152 | (date(2001, 4, 1), Duration(years=-1, months=-1, days=-1), date(2000, 2, 29)), | |
153 | (date(2000, 4, 1), Duration(years=-1, months=-1, days=-1), date(1999, 2, 28)), | |
154 | ( | |
155 | Duration(years=1, months=2), | |
156 | Duration(years=0, months=0, days=1), | |
157 | Duration(years=1, months=2, days=1), | |
158 | ), | |
159 | (Duration(years=-1, months=-1, days=-1), date(2000, 4, 1), date(1999, 2, 28)), | |
160 | (Duration(years=1, months=1, weeks=5), date(2000, 1, 30), date(2001, 4, 4)), | |
161 | (parse_duration("P1Y1M5W"), date(2000, 1, 30), date(2001, 4, 4)), | |
162 | (parse_duration("P0.5Y"), date(2000, 1, 30), None), | |
163 | ( | |
164 | Duration(years=1, months=1, hours=3), | |
165 | datetime(2000, 1, 30, 12, 15, 00), | |
166 | datetime(2001, 2, 28, 15, 15, 00), | |
167 | ), | |
168 | ( | |
169 | parse_duration("P1Y1MT3H"), | |
170 | datetime(2000, 1, 30, 12, 15, 00), | |
171 | datetime(2001, 2, 28, 15, 15, 00), | |
172 | ), | |
173 | ( | |
174 | Duration(years=1, months=2), | |
175 | timedelta(days=1), | |
176 | Duration(years=1, months=2, days=1), | |
177 | ), | |
178 | ( | |
179 | timedelta(days=1), | |
180 | Duration(years=1, months=2), | |
181 | Duration(years=1, months=2, days=1), | |
182 | ), | |
183 | (datetime(2008, 1, 1, 0, 2), Duration(months=1), datetime(2008, 2, 1, 0, 2)), | |
184 | ( | |
185 | datetime.strptime("200802", "%Y%M"), | |
186 | parse_duration("P1M"), | |
187 | datetime(2008, 2, 1, 0, 2), | |
188 | ), | |
189 | (datetime(2008, 2, 1), Duration(months=1), datetime(2008, 3, 1)), | |
190 | (datetime.strptime("200802", "%Y%m"), parse_duration("P1M"), datetime(2008, 3, 1)), | |
225 | 191 | # (date(2000, 1, 1), |
226 | 192 | # Duration(years=1.5), |
227 | 193 | # date(2001, 6, 1)), |
228 | 194 | # (date(2000, 1, 1), |
229 | 195 | # Duration(years=1, months=1.5), |
230 | 196 | # date(2001, 2, 14)), |
231 | ) | |
197 | ) | |
232 | 198 | |
233 | 199 | # A list of test cases of multiplications of durations |
234 | 200 | # are compared against a given expected result. |
235 | 201 | DATE_MUL_TEST_CASES = ( |
236 | (Duration(years=1, months=1), | |
237 | 3, | |
238 | Duration(years=3, months=3)), | |
239 | (Duration(years=1, months=1), | |
240 | -3, | |
241 | Duration(years=-3, months=-3)), | |
242 | (3, | |
243 | Duration(years=1, months=1), | |
244 | Duration(years=3, months=3)), | |
245 | (-3, | |
246 | Duration(years=1, months=1), | |
247 | Duration(years=-3, months=-3)), | |
248 | (5, | |
249 | Duration(years=2, minutes=40), | |
250 | Duration(years=10, hours=3, minutes=20)), | |
251 | (-5, | |
252 | Duration(years=2, minutes=40), | |
253 | Duration(years=-10, hours=-3, minutes=-20)), | |
254 | (7, | |
255 | Duration(years=1, months=2, weeks=40), | |
256 | Duration(years=8, months=2, weeks=280))) | |
202 | (Duration(years=1, months=1), 3, Duration(years=3, months=3)), | |
203 | (Duration(years=1, months=1), -3, Duration(years=-3, months=-3)), | |
204 | (3, Duration(years=1, months=1), Duration(years=3, months=3)), | |
205 | (-3, Duration(years=1, months=1), Duration(years=-3, months=-3)), | |
206 | (5, Duration(years=2, minutes=40), Duration(years=10, hours=3, minutes=20)), | |
207 | (-5, Duration(years=2, minutes=40), Duration(years=-10, hours=-3, minutes=-20)), | |
208 | (7, Duration(years=1, months=2, weeks=40), Duration(years=8, months=2, weeks=280)), | |
209 | ) | |
257 | 210 | |
258 | 211 | |
259 | 212 | class DurationTest(unittest.TestCase): |
260 | ''' | |
213 | """ | |
261 | 214 | This class tests various other aspects of the isoduration module, |
262 | 215 | which are not covered with the test cases listed above. |
263 | ''' | |
216 | """ | |
264 | 217 | |
265 | 218 | def test_associative(self): |
266 | ''' | |
219 | """ | |
267 | 220 | Adding 2 durations to a date is not associative. |
268 | ''' | |
221 | """ | |
269 | 222 | days1 = Duration(days=1) |
270 | 223 | months1 = Duration(months=1) |
271 | 224 | start = date(2000, 3, 30) |
274 | 227 | self.assertNotEqual(res1, res2) |
275 | 228 | |
276 | 229 | def test_typeerror(self): |
277 | ''' | |
230 | """ | |
278 | 231 | Test if TypError is raised with certain parameters. |
279 | ''' | |
232 | """ | |
280 | 233 | self.assertRaises(TypeError, parse_duration, date(2000, 1, 1)) |
281 | self.assertRaises(TypeError, operator.sub, Duration(years=1), | |
282 | date(2000, 1, 1)) | |
283 | self.assertRaises(TypeError, operator.sub, 'raise exc', | |
284 | Duration(years=1)) | |
285 | self.assertRaises(TypeError, operator.add, | |
286 | Duration(years=1, months=1, weeks=5), | |
287 | 'raise exception') | |
288 | self.assertRaises(TypeError, operator.add, 'raise exception', | |
289 | Duration(years=1, months=1, weeks=5)) | |
290 | self.assertRaises(TypeError, operator.mul, | |
291 | Duration(years=1, months=1, weeks=5), | |
292 | 'raise exception') | |
293 | self.assertRaises(TypeError, operator.mul, 'raise exception', | |
294 | Duration(years=1, months=1, weeks=5)) | |
295 | self.assertRaises(TypeError, operator.mul, | |
296 | Duration(years=1, months=1, weeks=5), | |
297 | 3.14) | |
298 | self.assertRaises(TypeError, operator.mul, 3.14, | |
299 | Duration(years=1, months=1, weeks=5)) | |
234 | self.assertRaises(TypeError, operator.sub, Duration(years=1), date(2000, 1, 1)) | |
235 | self.assertRaises(TypeError, operator.sub, "raise exc", Duration(years=1)) | |
236 | self.assertRaises( | |
237 | TypeError, | |
238 | operator.add, | |
239 | Duration(years=1, months=1, weeks=5), | |
240 | "raise exception", | |
241 | ) | |
242 | self.assertRaises( | |
243 | TypeError, | |
244 | operator.add, | |
245 | "raise exception", | |
246 | Duration(years=1, months=1, weeks=5), | |
247 | ) | |
248 | self.assertRaises( | |
249 | TypeError, | |
250 | operator.mul, | |
251 | Duration(years=1, months=1, weeks=5), | |
252 | "raise exception", | |
253 | ) | |
254 | self.assertRaises( | |
255 | TypeError, | |
256 | operator.mul, | |
257 | "raise exception", | |
258 | Duration(years=1, months=1, weeks=5), | |
259 | ) | |
260 | self.assertRaises( | |
261 | TypeError, operator.mul, Duration(years=1, months=1, weeks=5), 3.14 | |
262 | ) | |
263 | self.assertRaises( | |
264 | TypeError, operator.mul, 3.14, Duration(years=1, months=1, weeks=5) | |
265 | ) | |
300 | 266 | |
301 | 267 | def test_parseerror(self): |
302 | ''' | |
268 | """ | |
303 | 269 | Test for unparseable duration string. |
304 | ''' | |
305 | self.assertRaises(ISO8601Error, parse_duration, 'T10:10:10') | |
270 | """ | |
271 | self.assertRaises(ISO8601Error, parse_duration, "T10:10:10") | |
306 | 272 | |
307 | 273 | def test_repr(self): |
308 | ''' | |
274 | """ | |
309 | 275 | Test __repr__ and __str__ for Duration objects. |
310 | ''' | |
276 | """ | |
311 | 277 | dur = Duration(10, 10, years=10, months=10) |
312 | self.assertEqual('10 years, 10 months, 10 days, 0:00:10', str(dur)) | |
313 | self.assertEqual('isodate.duration.Duration(10, 10, 0,' | |
314 | ' years=10, months=10)', repr(dur)) | |
278 | self.assertEqual("10 years, 10 months, 10 days, 0:00:10", str(dur)) | |
279 | self.assertEqual( | |
280 | "isodate.duration.Duration(10, 10, 0," " years=10, months=10)", repr(dur) | |
281 | ) | |
315 | 282 | dur = Duration(months=0) |
316 | self.assertEqual('0:00:00', str(dur)) | |
283 | self.assertEqual("0:00:00", str(dur)) | |
317 | 284 | dur = Duration(months=1) |
318 | self.assertEqual('1 month, 0:00:00', str(dur)) | |
285 | self.assertEqual("1 month, 0:00:00", str(dur)) | |
319 | 286 | |
320 | 287 | def test_hash(self): |
321 | ''' | |
288 | """ | |
322 | 289 | Test __hash__ for Duration objects. |
323 | ''' | |
290 | """ | |
324 | 291 | dur1 = Duration(10, 10, years=10, months=10) |
325 | 292 | dur2 = Duration(9, 9, years=9, months=9) |
326 | 293 | dur3 = Duration(10, 10, years=10, months=10) |
335 | 302 | self.assertEqual(len(durSet), 2) |
336 | 303 | |
337 | 304 | def test_neg(self): |
338 | ''' | |
305 | """ | |
339 | 306 | Test __neg__ for Duration objects. |
340 | ''' | |
307 | """ | |
341 | 308 | self.assertEqual(-Duration(0), Duration(0)) |
342 | self.assertEqual(-Duration(years=1, months=1), | |
343 | Duration(years=-1, months=-1)) | |
309 | self.assertEqual(-Duration(years=1, months=1), Duration(years=-1, months=-1)) | |
344 | 310 | self.assertEqual(-Duration(years=1, months=1), Duration(months=-13)) |
345 | 311 | self.assertNotEqual(-Duration(years=1), timedelta(days=-365)) |
346 | 312 | self.assertNotEqual(-timedelta(days=365), Duration(years=-1)) |
349 | 315 | # self.assertNotEqual(-timedelta(days=10), -Duration(days=10)) |
350 | 316 | |
351 | 317 | def test_format(self): |
352 | ''' | |
318 | """ | |
353 | 319 | Test various other strftime combinations. |
354 | ''' | |
355 | self.assertEqual(duration_isoformat(Duration(0)), 'P0D') | |
356 | self.assertEqual(duration_isoformat(-Duration(0)), 'P0D') | |
357 | self.assertEqual(duration_isoformat(Duration(seconds=10)), 'PT10S') | |
358 | self.assertEqual(duration_isoformat(Duration(years=-1, months=-1)), | |
359 | '-P1Y1M') | |
360 | self.assertEqual(duration_isoformat(-Duration(years=1, months=1)), | |
361 | '-P1Y1M') | |
362 | self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)), | |
363 | 'P1Y1M') | |
364 | self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)), | |
365 | 'P1Y1M') | |
366 | dur = Duration(years=3, months=7, days=23, hours=5, minutes=25, | |
367 | milliseconds=330) | |
368 | self.assertEqual(duration_isoformat(dur), 'P3Y7M23DT5H25M0.33S') | |
369 | self.assertEqual(duration_isoformat(-dur), '-P3Y7M23DT5H25M0.33S') | |
320 | """ | |
321 | self.assertEqual(duration_isoformat(Duration(0)), "P0D") | |
322 | self.assertEqual(duration_isoformat(-Duration(0)), "P0D") | |
323 | self.assertEqual(duration_isoformat(Duration(seconds=10)), "PT10S") | |
324 | self.assertEqual(duration_isoformat(Duration(years=-1, months=-1)), "-P1Y1M") | |
325 | self.assertEqual(duration_isoformat(-Duration(years=1, months=1)), "-P1Y1M") | |
326 | self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)), "P1Y1M") | |
327 | self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)), "P1Y1M") | |
328 | dur = Duration( | |
329 | years=3, months=7, days=23, hours=5, minutes=25, milliseconds=330 | |
330 | ) | |
331 | self.assertEqual(duration_isoformat(dur), "P3Y7M23DT5H25M0.33S") | |
332 | self.assertEqual(duration_isoformat(-dur), "-P3Y7M23DT5H25M0.33S") | |
370 | 333 | |
371 | 334 | def test_equal(self): |
372 | ''' | |
335 | """ | |
373 | 336 | Test __eq__ and __ne__ methods. |
374 | ''' | |
375 | self.assertEqual(Duration(years=1, months=1), | |
376 | Duration(years=1, months=1)) | |
337 | """ | |
338 | self.assertEqual(Duration(years=1, months=1), Duration(years=1, months=1)) | |
377 | 339 | self.assertEqual(Duration(years=1, months=1), Duration(months=13)) |
378 | self.assertNotEqual(Duration(years=1, months=2), | |
379 | Duration(years=1, months=1)) | |
340 | self.assertNotEqual(Duration(years=1, months=2), Duration(years=1, months=1)) | |
380 | 341 | self.assertNotEqual(Duration(years=1, months=1), Duration(months=14)) |
381 | 342 | self.assertNotEqual(Duration(years=1), timedelta(days=365)) |
382 | self.assertFalse(Duration(years=1, months=1) != | |
383 | Duration(years=1, months=1)) | |
343 | self.assertFalse(Duration(years=1, months=1) != Duration(years=1, months=1)) | |
384 | 344 | self.assertFalse(Duration(years=1, months=1) != Duration(months=13)) |
385 | self.assertTrue(Duration(years=1, months=2) != | |
386 | Duration(years=1, months=1)) | |
345 | self.assertTrue(Duration(years=1, months=2) != Duration(years=1, months=1)) | |
387 | 346 | self.assertTrue(Duration(years=1, months=1) != Duration(months=14)) |
388 | 347 | self.assertTrue(Duration(years=1) != timedelta(days=365)) |
389 | 348 | self.assertEqual(Duration(days=1), timedelta(days=1)) |
392 | 351 | # self.assertNotEqual(timedelta(days=1), Duration(days=1)) |
393 | 352 | |
394 | 353 | def test_totimedelta(self): |
395 | ''' | |
354 | """ | |
396 | 355 | Test conversion form Duration to timedelta. |
397 | ''' | |
356 | """ | |
398 | 357 | dur = Duration(years=1, months=2, days=10) |
399 | self.assertEqual(dur.totimedelta(datetime(1998, 2, 25)), | |
400 | timedelta(434)) | |
358 | self.assertEqual(dur.totimedelta(datetime(1998, 2, 25)), timedelta(434)) | |
401 | 359 | # leap year has one day more in february |
402 | self.assertEqual(dur.totimedelta(datetime(2000, 2, 25)), | |
403 | timedelta(435)) | |
360 | self.assertEqual(dur.totimedelta(datetime(2000, 2, 25)), timedelta(435)) | |
404 | 361 | dur = Duration(months=2) |
405 | 362 | # march is longer than february, but april is shorter than |
406 | 363 | # march (cause only one day difference compared to 2) |
418 | 375 | """ |
419 | 376 | |
420 | 377 | class TestParseDuration(unittest.TestCase): |
421 | ''' | |
378 | """ | |
422 | 379 | A test case template to parse an ISO duration string into a |
423 | 380 | timedelta or Duration object. |
424 | ''' | |
381 | """ | |
425 | 382 | |
426 | 383 | def test_parse(self): |
427 | ''' | |
384 | """ | |
428 | 385 | Parse an ISO duration string and compare it to the expected value. |
429 | ''' | |
386 | """ | |
430 | 387 | result = parse_duration(durationstring) |
431 | 388 | self.assertEqual(result, expectation) |
432 | 389 | |
433 | 390 | def test_format(self): |
434 | ''' | |
391 | """ | |
435 | 392 | Take duration/timedelta object and create ISO string from it. |
436 | 393 | This is the reverse test to test_parse. |
437 | ''' | |
394 | """ | |
438 | 395 | if altstr: |
439 | self.assertEqual(duration_isoformat(expectation, format), | |
440 | altstr) | |
396 | self.assertEqual(duration_isoformat(expectation, format), altstr) | |
441 | 397 | else: |
442 | 398 | # if durationstring == '-P2W': |
443 | 399 | # import pdb; pdb.set_trace() |
444 | self.assertEqual(duration_isoformat(expectation, format), | |
445 | durationstring) | |
400 | self.assertEqual( | |
401 | duration_isoformat(expectation, format), durationstring | |
402 | ) | |
446 | 403 | |
447 | 404 | return unittest.TestLoader().loadTestsFromTestCase(TestParseDuration) |
448 | 405 | |
461 | 418 | ressub = parse_duration(ressub) |
462 | 419 | |
463 | 420 | class TestMathDuration(unittest.TestCase): |
464 | ''' | |
421 | """ | |
465 | 422 | A test case template test addition, subtraction and > |
466 | 423 | operators for Duration objects. |
467 | ''' | |
424 | """ | |
468 | 425 | |
469 | 426 | def test_add(self): |
470 | ''' | |
427 | """ | |
471 | 428 | Test operator + (__add__, __radd__) |
472 | ''' | |
429 | """ | |
473 | 430 | self.assertEqual(dur1 + dur2, resadd) |
474 | 431 | |
475 | 432 | def test_sub(self): |
476 | ''' | |
433 | """ | |
477 | 434 | Test operator - (__sub__, __rsub__) |
478 | ''' | |
435 | """ | |
479 | 436 | self.assertEqual(dur1 - dur2, ressub) |
480 | 437 | |
481 | 438 | def test_ge(self): |
482 | ''' | |
439 | """ | |
483 | 440 | Test operator > and < |
484 | ''' | |
441 | """ | |
442 | ||
485 | 443 | def dogetest(): |
486 | ''' Test greater than.''' | |
444 | """Test greater than.""" | |
487 | 445 | return dur1 > dur2 |
488 | 446 | |
489 | 447 | def doletest(): |
490 | ''' Test less than.''' | |
448 | """Test less than.""" | |
491 | 449 | return dur1 < dur2 |
450 | ||
492 | 451 | if resge is None: |
493 | 452 | self.assertRaises(TypeError, dogetest) |
494 | 453 | self.assertRaises(TypeError, doletest) |
508 | 467 | """ |
509 | 468 | |
510 | 469 | class TestDateCalc(unittest.TestCase): |
511 | ''' | |
470 | """ | |
512 | 471 | A test case template test addition, subtraction |
513 | 472 | operators for Duration objects. |
514 | ''' | |
473 | """ | |
515 | 474 | |
516 | 475 | def test_add(self): |
517 | ''' | |
476 | """ | |
518 | 477 | Test operator +. |
519 | ''' | |
478 | """ | |
520 | 479 | self.assertEqual(start + tdelta, start + duration) |
521 | 480 | |
522 | 481 | def test_sub(self): |
523 | ''' | |
482 | """ | |
524 | 483 | Test operator -. |
525 | ''' | |
484 | """ | |
526 | 485 | self.assertEqual(start - tdelta, start - duration) |
527 | 486 | |
528 | 487 | return unittest.TestLoader().loadTestsFromTestCase(TestDateCalc) |
537 | 496 | """ |
538 | 497 | |
539 | 498 | class TestDateCalc(unittest.TestCase): |
540 | ''' | |
499 | """ | |
541 | 500 | A test case template test addition operators for Duration objects. |
542 | ''' | |
501 | """ | |
543 | 502 | |
544 | 503 | def test_calc(self): |
545 | ''' | |
504 | """ | |
546 | 505 | Test operator +. |
547 | ''' | |
506 | """ | |
548 | 507 | if expectation is None: |
549 | 508 | self.assertRaises(ValueError, operator.add, start, duration) |
550 | 509 | else: |
562 | 521 | """ |
563 | 522 | |
564 | 523 | class TestDateMul(unittest.TestCase): |
565 | ''' | |
524 | """ | |
566 | 525 | A test case template test addition operators for Duration objects. |
567 | ''' | |
526 | """ | |
568 | 527 | |
569 | 528 | def test_mul(self): |
570 | ''' | |
529 | """ | |
571 | 530 | Test operator *. |
572 | ''' | |
531 | """ | |
573 | 532 | self.assertEqual(operand1 * operand2, expectation) |
574 | 533 | |
575 | 534 | return unittest.TestLoader().loadTestsFromTestCase(TestDateMul) |
576 | 535 | |
577 | 536 | |
578 | 537 | def test_suite(): |
579 | ''' | |
538 | """ | |
580 | 539 | Return a test suite containing all test defined above. |
581 | ''' | |
540 | """ | |
582 | 541 | suite = unittest.TestSuite() |
583 | for durationstring, (expectation, format, | |
584 | altstr) in PARSE_TEST_CASES.items(): | |
585 | suite.addTest(create_parsetestcase(durationstring, expectation, | |
586 | format, altstr)) | |
542 | for durationstring, (expectation, format, altstr) in PARSE_TEST_CASES.items(): | |
543 | suite.addTest(create_parsetestcase(durationstring, expectation, format, altstr)) | |
587 | 544 | for testdata in MATH_TEST_CASES: |
588 | 545 | suite.addTest(create_mathtestcase(*testdata)) |
589 | 546 | for testdata in DATE_TEST_CASES: |
601 | 558 | return test_suite() |
602 | 559 | |
603 | 560 | |
604 | if __name__ == '__main__': | |
605 | unittest.main(defaultTest='test_suite') | |
561 | if __name__ == "__main__": | |
562 | unittest.main(defaultTest="test_suite") |
0 | 0 | import unittest |
1 | 1 | |
2 | from six.moves import cPickle as pickle | |
2 | import pickle | |
3 | 3 | |
4 | 4 | import isodate |
5 | 5 | |
6 | 6 | |
7 | 7 | class TestPickle(unittest.TestCase): |
8 | ''' | |
8 | """ | |
9 | 9 | A test case template to parse an ISO datetime string into a |
10 | 10 | datetime object. |
11 | ''' | |
11 | """ | |
12 | 12 | |
13 | 13 | def test_pickle_datetime(self): |
14 | ''' | |
14 | """ | |
15 | 15 | Parse an ISO datetime string and compare it to the expected value. |
16 | ''' | |
17 | dti = isodate.parse_datetime('2012-10-26T09:33+00:00') | |
16 | """ | |
17 | dti = isodate.parse_datetime("2012-10-26T09:33+00:00") | |
18 | 18 | for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): |
19 | 19 | pikl = pickle.dumps(dti, proto) |
20 | self.assertEqual(dti, pickle.loads(pikl), | |
21 | "pickle proto %d failed" % proto) | |
20 | self.assertEqual(dti, pickle.loads(pikl), "pickle proto %d failed" % proto) | |
22 | 21 | |
23 | 22 | def test_pickle_duration(self): |
24 | ''' | |
23 | """ | |
25 | 24 | Pickle / unpickle duration objects. |
26 | ''' | |
25 | """ | |
27 | 26 | from isodate.duration import Duration |
27 | ||
28 | 28 | dur = Duration() |
29 | 29 | failed = [] |
30 | 30 | for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): |
34 | 34 | raise Exception("not equal") |
35 | 35 | except Exception as e: |
36 | 36 | failed.append("pickle proto %d failed (%s)" % (proto, repr(e))) |
37 | self.assertEqual(len(failed), 0, "pickle protos failed: %s" % | |
38 | str(failed)) | |
37 | self.assertEqual(len(failed), 0, "pickle protos failed: %s" % str(failed)) | |
39 | 38 | |
40 | 39 | def test_pickle_utc(self): |
41 | ''' | |
40 | """ | |
42 | 41 | isodate.UTC objects remain the same after pickling. |
43 | ''' | |
42 | """ | |
44 | 43 | self.assertTrue(isodate.UTC is pickle.loads(pickle.dumps(isodate.UTC))) |
45 | 44 | |
46 | 45 | |
47 | 46 | def test_suite(): |
48 | ''' | |
47 | """ | |
49 | 48 | Construct a TestSuite instance for all test cases. |
50 | ''' | |
49 | """ | |
51 | 50 | suite = unittest.TestSuite() |
52 | 51 | suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestPickle)) |
53 | 52 | return suite |
58 | 57 | return test_suite() |
59 | 58 | |
60 | 59 | |
61 | if __name__ == '__main__': | |
62 | unittest.main(defaultTest='test_suite') | |
60 | if __name__ == "__main__": | |
61 | unittest.main(defaultTest="test_suite") |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | Test cases for the isodate module. |
28 | ''' | |
2 | """ | |
29 | 3 | import unittest |
30 | 4 | import time |
31 | 5 | from datetime import datetime, timedelta |
35 | 9 | from isodate import tzinfo |
36 | 10 | |
37 | 11 | |
38 | TEST_CASES = ((datetime(2012, 12, 25, 13, 30, 0, 0, LOCAL), DT_EXT_COMPLETE, | |
39 | "2012-12-25T13:30:00+10:00"), | |
40 | # DST ON | |
41 | (datetime(1999, 12, 25, 13, 30, 0, 0, LOCAL), DT_EXT_COMPLETE, | |
42 | "1999-12-25T13:30:00+11:00"), | |
43 | # microseconds | |
44 | (datetime(2012, 10, 12, 8, 29, 46, 69178), | |
45 | "%Y-%m-%dT%H:%M:%S.%f", | |
46 | "2012-10-12T08:29:46.069178"), | |
47 | (datetime(2012, 10, 12, 8, 29, 46, 691780), | |
48 | "%Y-%m-%dT%H:%M:%S.%f", | |
49 | "2012-10-12T08:29:46.691780"), | |
50 | ) | |
12 | TEST_CASES = ( | |
13 | ( | |
14 | datetime(2012, 12, 25, 13, 30, 0, 0, LOCAL), | |
15 | DT_EXT_COMPLETE, | |
16 | "2012-12-25T13:30:00+10:00", | |
17 | ), | |
18 | # DST ON | |
19 | ( | |
20 | datetime(1999, 12, 25, 13, 30, 0, 0, LOCAL), | |
21 | DT_EXT_COMPLETE, | |
22 | "1999-12-25T13:30:00+11:00", | |
23 | ), | |
24 | # microseconds | |
25 | ( | |
26 | datetime(2012, 10, 12, 8, 29, 46, 69178), | |
27 | "%Y-%m-%dT%H:%M:%S.%f", | |
28 | "2012-10-12T08:29:46.069178", | |
29 | ), | |
30 | ( | |
31 | datetime(2012, 10, 12, 8, 29, 46, 691780), | |
32 | "%Y-%m-%dT%H:%M:%S.%f", | |
33 | "2012-10-12T08:29:46.691780", | |
34 | ), | |
35 | ) | |
51 | 36 | |
52 | 37 | |
53 | 38 | def create_testcase(dt, format, expectation): |
59 | 44 | """ |
60 | 45 | |
61 | 46 | class TestDate(unittest.TestCase): |
62 | ''' | |
47 | """ | |
63 | 48 | A test case template to test ISO date formatting. |
64 | ''' | |
49 | """ | |
65 | 50 | |
66 | 51 | # local time zone mock function |
67 | 52 | def localtime_mock(self, secs): |
69 | 54 | mock time.localtime so that it always returns a time_struct with |
70 | 55 | tm_idst=1 |
71 | 56 | """ |
72 | tt = self.ORIG['localtime'](secs) | |
57 | tt = self.ORIG["localtime"](secs) | |
73 | 58 | # befor 2000 everything is dst, after 2000 no dst. |
74 | 59 | if tt.tm_year < 2000: |
75 | 60 | dst = 1 |
76 | 61 | else: |
77 | 62 | dst = 0 |
78 | tt = (tt.tm_year, tt.tm_mon, tt.tm_mday, | |
79 | tt.tm_hour, tt.tm_min, tt.tm_sec, | |
80 | tt.tm_wday, tt.tm_yday, dst) | |
63 | tt = ( | |
64 | tt.tm_year, | |
65 | tt.tm_mon, | |
66 | tt.tm_mday, | |
67 | tt.tm_hour, | |
68 | tt.tm_min, | |
69 | tt.tm_sec, | |
70 | tt.tm_wday, | |
71 | tt.tm_yday, | |
72 | dst, | |
73 | ) | |
81 | 74 | return time.struct_time(tt) |
82 | 75 | |
83 | 76 | def setUp(self): |
84 | 77 | self.ORIG = {} |
85 | self.ORIG['STDOFFSET'] = tzinfo.STDOFFSET | |
86 | self.ORIG['DSTOFFSET'] = tzinfo.DSTOFFSET | |
87 | self.ORIG['DSTDIFF'] = tzinfo.DSTDIFF | |
88 | self.ORIG['localtime'] = time.localtime | |
78 | self.ORIG["STDOFFSET"] = tzinfo.STDOFFSET | |
79 | self.ORIG["DSTOFFSET"] = tzinfo.DSTOFFSET | |
80 | self.ORIG["DSTDIFF"] = tzinfo.DSTDIFF | |
81 | self.ORIG["localtime"] = time.localtime | |
89 | 82 | # ovveride all saved values with fixtures. |
90 | 83 | # calculate LOCAL TZ offset, so that this test runs in |
91 | 84 | # every time zone |
96 | 89 | |
97 | 90 | def tearDown(self): |
98 | 91 | # restore test fixtures |
99 | tzinfo.STDOFFSET = self.ORIG['STDOFFSET'] | |
100 | tzinfo.DSTOFFSET = self.ORIG['DSTOFFSET'] | |
101 | tzinfo.DSTDIFF = self.ORIG['DSTDIFF'] | |
102 | time.localtime = self.ORIG['localtime'] | |
92 | tzinfo.STDOFFSET = self.ORIG["STDOFFSET"] | |
93 | tzinfo.DSTOFFSET = self.ORIG["DSTOFFSET"] | |
94 | tzinfo.DSTDIFF = self.ORIG["DSTDIFF"] | |
95 | time.localtime = self.ORIG["localtime"] | |
103 | 96 | |
104 | 97 | def test_format(self): |
105 | ''' | |
98 | """ | |
106 | 99 | Take date object and create ISO string from it. |
107 | 100 | This is the reverse test to test_parse. |
108 | ''' | |
101 | """ | |
109 | 102 | if expectation is None: |
110 | self.assertRaises(AttributeError, | |
111 | strftime(dt, format)) | |
103 | self.assertRaises(AttributeError, strftime(dt, format)) | |
112 | 104 | else: |
113 | self.assertEqual(strftime(dt, format), | |
114 | expectation) | |
105 | self.assertEqual(strftime(dt, format), expectation) | |
115 | 106 | |
116 | 107 | return unittest.TestLoader().loadTestsFromTestCase(TestDate) |
117 | 108 | |
118 | 109 | |
119 | 110 | def test_suite(): |
120 | ''' | |
111 | """ | |
121 | 112 | Construct a TestSuite instance for all test cases. |
122 | ''' | |
113 | """ | |
123 | 114 | suite = unittest.TestSuite() |
124 | 115 | for dt, format, expectation in TEST_CASES: |
125 | 116 | suite.addTest(create_testcase(dt, format, expectation)) |
131 | 122 | return test_suite() |
132 | 123 | |
133 | 124 | |
134 | if __name__ == '__main__': | |
135 | unittest.main(defaultTest='test_suite') | |
125 | if __name__ == "__main__": | |
126 | unittest.main(defaultTest="test_suite") |
0 | ############################################################################## | |
1 | # Copyright 2009, Gerhard Weis | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright notice, | |
10 | # this list of conditions and the following disclaimer in the documentation | |
11 | # and/or other materials provided with the distribution. | |
12 | # * Neither the name of the authors nor the names of its contributors | |
13 | # may be used to endorse or promote products derived from this software | |
14 | # without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT | |
25 | ############################################################################## | |
26 | ''' | |
0 | """ | |
27 | 1 | Test cases for the isotime module. |
28 | ''' | |
2 | """ | |
29 | 3 | import unittest |
30 | 4 | from datetime import time |
31 | 5 | |
38 | 12 | # the following list contains tuples of ISO time strings and the expected |
39 | 13 | # result from the parse_time method. A result of None means an ISO8601Error |
40 | 14 | # is expected. |
41 | TEST_CASES = [('232050', time(23, 20, 50), TIME_BAS_COMPLETE + TZ_BAS), | |
42 | ('23:20:50', time(23, 20, 50), TIME_EXT_COMPLETE + TZ_EXT), | |
43 | ('2320', time(23, 20), TIME_BAS_MINUTE), | |
44 | ('23:20', time(23, 20), TIME_EXT_MINUTE), | |
45 | ('23', time(23), TIME_HOUR), | |
46 | ('232050,5', time(23, 20, 50, 500000), None), | |
47 | ('23:20:50.5', time(23, 20, 50, 500000), None), | |
48 | # test precision | |
49 | ('15:33:42.123456', time(15, 33, 42, 123456), None), | |
50 | ('15:33:42.1234564', time(15, 33, 42, 123456), None), | |
51 | ('15:33:42.1234557', time(15, 33, 42, 123456), None), | |
52 | ('2320,8', time(23, 20, 48), None), | |
53 | ('23:20,8', time(23, 20, 48), None), | |
54 | ('23,3', time(23, 18), None), | |
55 | ('232030Z', time(23, 20, 30, tzinfo=UTC), | |
56 | TIME_BAS_COMPLETE + TZ_BAS), | |
57 | ('2320Z', time(23, 20, tzinfo=UTC), TIME_BAS_MINUTE + TZ_BAS), | |
58 | ('23Z', time(23, tzinfo=UTC), TIME_HOUR + TZ_BAS), | |
59 | ('23:20:30Z', time(23, 20, 30, tzinfo=UTC), | |
60 | TIME_EXT_COMPLETE + TZ_EXT), | |
61 | ('23:20Z', time(23, 20, tzinfo=UTC), TIME_EXT_MINUTE + TZ_EXT), | |
62 | ('152746+0100', time(15, 27, 46, | |
63 | tzinfo=FixedOffset(1, 0, '+0100')), TIME_BAS_COMPLETE + TZ_BAS), | |
64 | ('152746-0500', time(15, 27, 46, | |
65 | tzinfo=FixedOffset(-5, 0, '-0500')), | |
66 | TIME_BAS_COMPLETE + TZ_BAS), | |
67 | ('152746+01', time(15, 27, 46, | |
68 | tzinfo=FixedOffset(1, 0, '+01:00')), | |
69 | TIME_BAS_COMPLETE + TZ_HOUR), | |
70 | ('152746-05', time(15, 27, 46, | |
71 | tzinfo=FixedOffset(-5, -0, '-05:00')), | |
72 | TIME_BAS_COMPLETE + TZ_HOUR), | |
73 | ('15:27:46+01:00', time(15, 27, 46, | |
74 | tzinfo=FixedOffset(1, 0, '+01:00')), | |
75 | TIME_EXT_COMPLETE + TZ_EXT), | |
76 | ('15:27:46-05:00', time(15, 27, 46, | |
77 | tzinfo=FixedOffset(-5, -0, '-05:00')), | |
78 | TIME_EXT_COMPLETE + TZ_EXT), | |
79 | ('15:27:46+01', time(15, 27, 46, | |
80 | tzinfo=FixedOffset(1, 0, '+01:00')), | |
81 | TIME_EXT_COMPLETE + TZ_HOUR), | |
82 | ('15:27:46-05', time(15, 27, 46, | |
83 | tzinfo=FixedOffset(-5, -0, '-05:00')), | |
84 | TIME_EXT_COMPLETE + TZ_HOUR), | |
85 | ('15:27:46-05:30', time(15, 27, 46, | |
86 | tzinfo=FixedOffset(-5, -30, '-05:30')), | |
87 | TIME_EXT_COMPLETE + TZ_EXT), | |
88 | ('15:27:46-0545', time(15, 27, 46, | |
89 | tzinfo=FixedOffset(-5, -45, '-0545')), | |
90 | TIME_EXT_COMPLETE + TZ_BAS), | |
91 | ('1:17:30', None, TIME_EXT_COMPLETE)] | |
15 | TEST_CASES = [ | |
16 | ("232050", time(23, 20, 50), TIME_BAS_COMPLETE + TZ_BAS), | |
17 | ("23:20:50", time(23, 20, 50), TIME_EXT_COMPLETE + TZ_EXT), | |
18 | ("2320", time(23, 20), TIME_BAS_MINUTE), | |
19 | ("23:20", time(23, 20), TIME_EXT_MINUTE), | |
20 | ("23", time(23), TIME_HOUR), | |
21 | ("232050,5", time(23, 20, 50, 500000), None), | |
22 | ("23:20:50.5", time(23, 20, 50, 500000), None), | |
23 | # test precision | |
24 | ("15:33:42.123456", time(15, 33, 42, 123456), None), | |
25 | ("15:33:42.1234564", time(15, 33, 42, 123456), None), | |
26 | ("15:33:42.1234557", time(15, 33, 42, 123455), None), | |
27 | ( | |
28 | "10:59:59.9999999Z", | |
29 | time(10, 59, 59, 999999, tzinfo=UTC), | |
30 | None, | |
31 | ), # TIME_EXT_COMPLETE + TZ_EXT), | |
32 | ("2320,8", time(23, 20, 48), None), | |
33 | ("23:20,8", time(23, 20, 48), None), | |
34 | ("23,3", time(23, 18), None), | |
35 | ("232030Z", time(23, 20, 30, tzinfo=UTC), TIME_BAS_COMPLETE + TZ_BAS), | |
36 | ("2320Z", time(23, 20, tzinfo=UTC), TIME_BAS_MINUTE + TZ_BAS), | |
37 | ("23Z", time(23, tzinfo=UTC), TIME_HOUR + TZ_BAS), | |
38 | ("23:20:30Z", time(23, 20, 30, tzinfo=UTC), TIME_EXT_COMPLETE + TZ_EXT), | |
39 | ("23:20Z", time(23, 20, tzinfo=UTC), TIME_EXT_MINUTE + TZ_EXT), | |
40 | ( | |
41 | "152746+0100", | |
42 | time(15, 27, 46, tzinfo=FixedOffset(1, 0, "+0100")), | |
43 | TIME_BAS_COMPLETE + TZ_BAS, | |
44 | ), | |
45 | ( | |
46 | "152746-0500", | |
47 | time(15, 27, 46, tzinfo=FixedOffset(-5, 0, "-0500")), | |
48 | TIME_BAS_COMPLETE + TZ_BAS, | |
49 | ), | |
50 | ( | |
51 | "152746+01", | |
52 | time(15, 27, 46, tzinfo=FixedOffset(1, 0, "+01:00")), | |
53 | TIME_BAS_COMPLETE + TZ_HOUR, | |
54 | ), | |
55 | ( | |
56 | "152746-05", | |
57 | time(15, 27, 46, tzinfo=FixedOffset(-5, -0, "-05:00")), | |
58 | TIME_BAS_COMPLETE + TZ_HOUR, | |
59 | ), | |
60 | ( | |
61 | "15:27:46+01:00", | |
62 | time(15, 27, 46, tzinfo=FixedOffset(1, 0, "+01:00")), | |
63 | TIME_EXT_COMPLETE + TZ_EXT, | |
64 | ), | |
65 | ( | |
66 | "15:27:46-05:00", | |
67 | time(15, 27, 46, tzinfo=FixedOffset(-5, -0, "-05:00")), | |
68 | TIME_EXT_COMPLETE + TZ_EXT, | |
69 | ), | |
70 | ( | |
71 | "15:27:46+01", | |
72 | time(15, 27, 46, tzinfo=FixedOffset(1, 0, "+01:00")), | |
73 | TIME_EXT_COMPLETE + TZ_HOUR, | |
74 | ), | |
75 | ( | |
76 | "15:27:46-05", | |
77 | time(15, 27, 46, tzinfo=FixedOffset(-5, -0, "-05:00")), | |
78 | TIME_EXT_COMPLETE + TZ_HOUR, | |
79 | ), | |
80 | ( | |
81 | "15:27:46-05:30", | |
82 | time(15, 27, 46, tzinfo=FixedOffset(-5, -30, "-05:30")), | |
83 | TIME_EXT_COMPLETE + TZ_EXT, | |
84 | ), | |
85 | ( | |
86 | "15:27:46-0545", | |
87 | time(15, 27, 46, tzinfo=FixedOffset(-5, -45, "-0545")), | |
88 | TIME_EXT_COMPLETE + TZ_BAS, | |
89 | ), | |
90 | ("1:17:30", None, TIME_EXT_COMPLETE), | |
91 | ] | |
92 | 92 | |
93 | 93 | |
94 | 94 | def create_testcase(timestring, expectation, format): |
100 | 100 | """ |
101 | 101 | |
102 | 102 | class TestTime(unittest.TestCase): |
103 | ''' | |
103 | """ | |
104 | 104 | A test case template to parse an ISO time string into a time |
105 | 105 | object. |
106 | ''' | |
106 | """ | |
107 | 107 | |
108 | 108 | def test_parse(self): |
109 | ''' | |
109 | """ | |
110 | 110 | Parse an ISO time string and compare it to the expected value. |
111 | ''' | |
111 | """ | |
112 | 112 | if expectation is None: |
113 | 113 | self.assertRaises(ISO8601Error, parse_time, timestring) |
114 | 114 | else: |
116 | 116 | self.assertEqual(result, expectation) |
117 | 117 | |
118 | 118 | def test_format(self): |
119 | ''' | |
119 | """ | |
120 | 120 | Take time object and create ISO string from it. |
121 | 121 | This is the reverse test to test_parse. |
122 | ''' | |
122 | """ | |
123 | 123 | if expectation is None: |
124 | self.assertRaises(AttributeError, | |
125 | time_isoformat, expectation, format) | |
124 | self.assertRaises(AttributeError, time_isoformat, expectation, format) | |
126 | 125 | elif format is not None: |
127 | self.assertEqual(time_isoformat(expectation, format), | |
128 | timestring) | |
126 | self.assertEqual(time_isoformat(expectation, format), timestring) | |
129 | 127 | |
130 | 128 | return unittest.TestLoader().loadTestsFromTestCase(TestTime) |
131 | 129 | |
132 | 130 | |
133 | 131 | def test_suite(): |
134 | ''' | |
132 | """ | |
135 | 133 | Construct a TestSuite instance for all test cases. |
136 | ''' | |
134 | """ | |
137 | 135 | suite = unittest.TestSuite() |
138 | 136 | for timestring, expectation, format in TEST_CASES: |
139 | 137 | suite.addTest(create_testcase(timestring, expectation, format)) |
145 | 143 | return test_suite() |
146 | 144 | |
147 | 145 | |
148 | if __name__ == '__main__': | |
149 | unittest.main(defaultTest='test_suite') | |
146 | if __name__ == "__main__": | |
147 | unittest.main(defaultTest="test_suite") |
0 | ''' | |
0 | """ | |
1 | 1 | This module provides some datetime.tzinfo implementations. |
2 | 2 | |
3 | 3 | All those classes are taken from the Python documentation. |
4 | ''' | |
4 | """ | |
5 | 5 | from datetime import timedelta, tzinfo |
6 | 6 | import time |
7 | 7 | |
10 | 10 | |
11 | 11 | |
12 | 12 | class Utc(tzinfo): |
13 | '''UTC | |
13 | """UTC | |
14 | 14 | |
15 | 15 | Universal time coordinated time zone. |
16 | ''' | |
16 | """ | |
17 | 17 | |
18 | 18 | def utcoffset(self, dt): |
19 | ''' | |
19 | """ | |
20 | 20 | Return offset from UTC in minutes east of UTC, which is ZERO for UTC. |
21 | ''' | |
21 | """ | |
22 | 22 | return ZERO |
23 | 23 | |
24 | 24 | def tzname(self, dt): |
25 | ''' | |
25 | """ | |
26 | 26 | Return the time zone name corresponding to the datetime object dt, |
27 | 27 | as a string. |
28 | ''' | |
28 | """ | |
29 | 29 | return "UTC" |
30 | 30 | |
31 | 31 | def dst(self, dt): |
32 | ''' | |
32 | """ | |
33 | 33 | Return the daylight saving time (DST) adjustment, in minutes east |
34 | 34 | of UTC. |
35 | ''' | |
35 | """ | |
36 | 36 | return ZERO |
37 | 37 | |
38 | 38 | def __reduce__(self): |
39 | ''' | |
39 | """ | |
40 | 40 | When unpickling a Utc object, return the default instance below, UTC. |
41 | ''' | |
41 | """ | |
42 | 42 | return _Utc, () |
43 | 43 | |
44 | 44 | |
47 | 47 | |
48 | 48 | |
49 | 49 | def _Utc(): |
50 | ''' | |
50 | """ | |
51 | 51 | Helper function for unpickling a Utc object. |
52 | ''' | |
52 | """ | |
53 | 53 | return UTC |
54 | 54 | |
55 | 55 | |
56 | 56 | class FixedOffset(tzinfo): |
57 | ''' | |
57 | """ | |
58 | 58 | A class building tzinfo objects for fixed-offset time zones. |
59 | 59 | |
60 | 60 | Note that FixedOffset(0, 0, "UTC") or FixedOffset() is a different way to |
61 | 61 | build a UTC tzinfo object. |
62 | ''' | |
62 | """ | |
63 | 63 | |
64 | 64 | def __init__(self, offset_hours=0, offset_minutes=0, name="UTC"): |
65 | ''' | |
65 | """ | |
66 | 66 | Initialise an instance with time offset and name. |
67 | 67 | The time offset should be positive for time zones east of UTC |
68 | 68 | and negate for time zones west of UTC. |
69 | ''' | |
69 | """ | |
70 | 70 | self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes) |
71 | 71 | self.__name = name |
72 | 72 | |
73 | 73 | def utcoffset(self, dt): |
74 | ''' | |
74 | """ | |
75 | 75 | Return offset from UTC in minutes of UTC. |
76 | ''' | |
76 | """ | |
77 | 77 | return self.__offset |
78 | 78 | |
79 | 79 | def tzname(self, dt): |
80 | ''' | |
80 | """ | |
81 | 81 | Return the time zone name corresponding to the datetime object dt, as a |
82 | 82 | string. |
83 | ''' | |
83 | """ | |
84 | 84 | return self.__name |
85 | 85 | |
86 | 86 | def dst(self, dt): |
87 | ''' | |
87 | """ | |
88 | 88 | Return the daylight saving time (DST) adjustment, in minutes east of |
89 | 89 | UTC. |
90 | ''' | |
90 | """ | |
91 | 91 | return ZERO |
92 | 92 | |
93 | 93 | def __repr__(self): |
94 | ''' | |
94 | """ | |
95 | 95 | Return nicely formatted repr string. |
96 | ''' | |
96 | """ | |
97 | 97 | return "<FixedOffset %r>" % self.__name |
98 | 98 | |
99 | 99 | |
116 | 116 | """ |
117 | 117 | |
118 | 118 | def utcoffset(self, dt): |
119 | ''' | |
119 | """ | |
120 | 120 | Return offset from UTC in minutes of UTC. |
121 | ''' | |
121 | """ | |
122 | 122 | if self._isdst(dt): |
123 | 123 | return DSTOFFSET |
124 | 124 | else: |
125 | 125 | return STDOFFSET |
126 | 126 | |
127 | 127 | def dst(self, dt): |
128 | ''' | |
128 | """ | |
129 | 129 | Return daylight saving offset. |
130 | ''' | |
130 | """ | |
131 | 131 | if self._isdst(dt): |
132 | 132 | return DSTDIFF |
133 | 133 | else: |
134 | 134 | return ZERO |
135 | 135 | |
136 | 136 | def tzname(self, dt): |
137 | ''' | |
137 | """ | |
138 | 138 | Return the time zone name corresponding to the datetime object dt, as a |
139 | 139 | string. |
140 | ''' | |
140 | """ | |
141 | 141 | return time.tzname[self._isdst(dt)] |
142 | 142 | |
143 | 143 | def _isdst(self, dt): |
144 | ''' | |
144 | """ | |
145 | 145 | Returns true if DST is active for given datetime object dt. |
146 | ''' | |
147 | tt = (dt.year, dt.month, dt.day, | |
148 | dt.hour, dt.minute, dt.second, | |
149 | dt.weekday(), 0, -1) | |
146 | """ | |
147 | tt = ( | |
148 | dt.year, | |
149 | dt.month, | |
150 | dt.day, | |
151 | dt.hour, | |
152 | dt.minute, | |
153 | dt.second, | |
154 | dt.weekday(), | |
155 | 0, | |
156 | -1, | |
157 | ) | |
150 | 158 | stamp = time.mktime(tt) |
151 | 159 | tt = time.localtime(stamp) |
152 | 160 | return tt.tm_isdst > 0 |
0 | 0 | Metadata-Version: 2.1 |
1 | 1 | Name: isodate |
2 | Version: 0.6.1 | |
2 | Version: 0.7.0.dev0 | |
3 | 3 | Summary: An ISO 8601 date/time/duration parser and formatter |
4 | 4 | Home-page: https://github.com/gweis/isodate/ |
5 | 5 | Author: Gerhard Weis |
6 | 6 | Author-email: gerhard.weis@proclos.com |
7 | License: BSD | |
7 | License: BSD-3-Clause | |
8 | 8 | Platform: UNKNOWN |
9 | 9 | Classifier: Development Status :: 4 - Beta |
10 | 10 | Classifier: Intended Audience :: Developers |
11 | 11 | Classifier: License :: OSI Approved :: BSD License |
12 | 12 | Classifier: Operating System :: OS Independent |
13 | 13 | Classifier: Programming Language :: Python |
14 | Classifier: Programming Language :: Python :: 2 | |
15 | Classifier: Programming Language :: Python :: 2.7 | |
16 | 14 | Classifier: Programming Language :: Python :: 3 |
17 | Classifier: Programming Language :: Python :: 3.6 | |
18 | 15 | Classifier: Programming Language :: Python :: 3.7 |
19 | 16 | Classifier: Programming Language :: Python :: 3.8 |
20 | 17 | Classifier: Programming Language :: Python :: 3.9 |
22 | 19 | Classifier: Programming Language :: Python :: Implementation :: PyPy |
23 | 20 | Classifier: Topic :: Internet |
24 | 21 | Classifier: Topic :: Software Development :: Libraries :: Python Modules |
22 | License-File: LICENSE | |
25 | 23 | |
26 | 24 | |
27 | 25 | ISO 8601 date/time parser |
63 | 61 | Documentation |
64 | 62 | ------------- |
65 | 63 | |
66 | Currently there are four parsing methods available. | |
64 | The following parsing methods are available. | |
67 | 65 | * parse_time: |
68 | 66 | parses an ISO 8601 time string into a *time* object |
69 | 67 | * parse_date: |
108 | 106 | prior 1900. This method also understands how to format *datetime* and |
109 | 107 | *Duration* instances. |
110 | 108 | |
111 | Installation: | |
112 | ------------- | |
109 | Installation | |
110 | ------------ | |
113 | 111 | |
114 | 112 | This module can easily be installed with Python standard installation methods. |
115 | 113 | |
116 | 114 | Either use *python setup.py install* or in case you have *setuptools* or |
117 | 115 | *distribute* available, you can also use *easy_install*. |
118 | 116 | |
119 | Limitations: | |
120 | ------------ | |
117 | Limitations | |
118 | ----------- | |
121 | 119 | |
122 | 120 | * The parser accepts several date/time representation which should be invalid |
123 | 121 | according to ISO 8601 standard. |
129 | 127 | 1901-01-01. |
130 | 128 | 3. negative *Duration* and *timedelta* value are not fully supported yet. |
131 | 129 | |
132 | Further information: | |
133 | -------------------- | |
130 | Further information | |
131 | ------------------- | |
134 | 132 | |
135 | 133 | The doc strings and unit tests should provide rather detailed information about |
136 | 134 | the methods and their limitations. |
143 | 141 | CHANGES |
144 | 142 | ======= |
145 | 143 | |
144 | 0.7.0 (unreleased) | |
145 | ------------------ | |
146 | ||
147 | - drop end of life python versions | |
148 | - Don't match garbage characters at the end of parsed strings #16 (Gabriel de Perthuis) | |
149 | - Breaking: fractional seconds are cut off to microseconds (round down) | |
150 | - Allow control over return type of parse_duration #64 (Felix Claessen) | |
151 | ||
152 | ||
146 | 153 | 0.6.1 (2021-12-13) |
147 | 154 | ------------------ |
148 | 155 | |
149 | - support python 3.10 () | |
156 | - support python 3.10 (Hugo van Kemenade) | |
150 | 157 | - last version to support py 2.7 |
151 | 158 | |
152 | 159 |
0 | 0 | CHANGES.txt |
1 | LICENSE | |
1 | 2 | MANIFEST.in |
2 | 3 | README.rst |
3 | 4 | TODO.txt |
16 | 17 | src/isodate.egg-info/PKG-INFO |
17 | 18 | src/isodate.egg-info/SOURCES.txt |
18 | 19 | src/isodate.egg-info/dependency_links.txt |
19 | src/isodate.egg-info/requires.txt | |
20 | 20 | src/isodate.egg-info/top_level.txt |
21 | 21 | src/isodate/tests/__init__.py |
22 | 22 | src/isodate/tests/test_date.py |