Codebase list isodate / 18652e6
Import upstream version 0.6.1+git20211213.1.d0eb6b9 Debian Janitor 2 years ago
25 changed file(s) with 1308 addition(s) and 1473 deletion(s). Raw diff Collapse all Expand all
11 CHANGES
22 =======
33
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
413 0.6.1 (2021-12-13)
514 ------------------
615
7 - support python 3.10 ()
16 - support python 3.10 (Hugo van Kemenade)
817 - last version to support py 2.7
918
1019
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.
00 Metadata-Version: 2.1
11 Name: isodate
2 Version: 0.6.1
2 Version: 0.7.0.dev0
33 Summary: An ISO 8601 date/time/duration parser and formatter
44 Home-page: https://github.com/gweis/isodate/
55 Author: Gerhard Weis
66 Author-email: gerhard.weis@proclos.com
7 License: BSD
7 License: BSD-3-Clause
88 Platform: UNKNOWN
99 Classifier: Development Status :: 4 - Beta
1010 Classifier: Intended Audience :: Developers
1111 Classifier: License :: OSI Approved :: BSD License
1212 Classifier: Operating System :: OS Independent
1313 Classifier: Programming Language :: Python
14 Classifier: Programming Language :: Python :: 2
15 Classifier: Programming Language :: Python :: 2.7
1614 Classifier: Programming Language :: Python :: 3
17 Classifier: Programming Language :: Python :: 3.6
1815 Classifier: Programming Language :: Python :: 3.7
1916 Classifier: Programming Language :: Python :: 3.8
2017 Classifier: Programming Language :: Python :: 3.9
2219 Classifier: Programming Language :: Python :: Implementation :: PyPy
2320 Classifier: Topic :: Internet
2421 Classifier: Topic :: Software Development :: Libraries :: Python Modules
22 License-File: LICENSE
2523
2624
2725 ISO 8601 date/time parser
6361 Documentation
6462 -------------
6563
66 Currently there are four parsing methods available.
64 The following parsing methods are available.
6765 * parse_time:
6866 parses an ISO 8601 time string into a *time* object
6967 * parse_date:
108106 prior 1900. This method also understands how to format *datetime* and
109107 *Duration* instances.
110108
111 Installation:
112 -------------
109 Installation
110 ------------
113111
114112 This module can easily be installed with Python standard installation methods.
115113
116114 Either use *python setup.py install* or in case you have *setuptools* or
117115 *distribute* available, you can also use *easy_install*.
118116
119 Limitations:
120 ------------
117 Limitations
118 -----------
121119
122120 * The parser accepts several date/time representation which should be invalid
123121 according to ISO 8601 standard.
129127 1901-01-01.
130128 3. negative *Duration* and *timedelta* value are not fully supported yet.
131129
132 Further information:
133 --------------------
130 Further information
131 -------------------
134132
135133 The doc strings and unit tests should provide rather detailed information about
136134 the methods and their limitations.
143141 CHANGES
144142 =======
145143
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
146153 0.6.1 (2021-12-13)
147154 ------------------
148155
149 - support python 3.10 ()
156 - support python 3.10 (Hugo van Kemenade)
150157 - last version to support py 2.7
151158
152159
3737 Documentation
3838 -------------
3939
40 Currently there are four parsing methods available.
40 The following parsing methods are available.
4141 * parse_time:
4242 parses an ISO 8601 time string into a *time* object
4343 * parse_date:
8282 prior 1900. This method also understands how to format *datetime* and
8383 *Duration* instances.
8484
85 Installation:
86 -------------
85 Installation
86 ------------
8787
8888 This module can easily be installed with Python standard installation methods.
8989
9090 Either use *python setup.py install* or in case you have *setuptools* or
9191 *distribute* available, you can also use *easy_install*.
9292
93 Limitations:
94 ------------
93 Limitations
94 -----------
9595
9696 * The parser accepts several date/time representation which should be invalid
9797 according to ISO 8601 standard.
103103 1901-01-01.
104104 3. negative *Duration* and *timedelta* value are not fully supported yet.
105105
106 Further information:
107 --------------------
106 Further information
107 -------------------
108108
109109 The doc strings and unit tests should provide rather detailed information about
110110 the methods and their limitations.
00 #!/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 ##############################################################################
271 import os
282 from setuptools import setup
293
304
315 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()
338
349
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 """
271 Import all essential functions and constants to re-export them here for easy
282 access.
293
304 This module contains also various pre-defined ISO 8601 format strings.
31 '''
5 """
326 from isodate.isodates import parse_date, date_isoformat
337 from isodate.isotime import parse_time, time_isoformat
348 from isodate.isodatetime import parse_datetime, datetime_isoformat
5428 from isodate.isostrf import D_DEFAULT, D_WEEK, D_ALT_EXT, D_ALT_BAS
5529 from isodate.isostrf import D_ALT_BAS_ORD, D_ALT_EXT_ORD
5630
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 """
271 This module defines a Duration class.
282
293 The class Duration allows to define durations in years and months and can be
304 used as limited replacement for timedelta objects.
31 '''
5 """
326 from datetime import timedelta
337 from decimal import Decimal, ROUND_FLOOR
348
359
3610 def fquotmod(val, low, high):
37 '''
11 """
3812 A divmod function with boundaries.
3913
40 '''
14 """
4115 # assumes that all the maths is done with Decimals.
4216 # divmod for Decimal uses truncate instead of floor as builtin
4317 # divmod, so we have to do it manually here.
5125
5226
5327 def max_days_in_month(year, month):
54 '''
28 """
5529 Determines the number of days of a specific month in a specific year.
56 '''
30 """
5731 if month in (1, 3, 5, 7, 8, 10, 12):
5832 return 31
5933 if month in (4, 6, 9, 11):
6438
6539
6640 class Duration(object):
67 '''
41 """
6842 A class which represents a duration.
6943
7044 The difference to datetime.timedelta is, that this class handles also
8458
8559 The algorithm to add a duration to a date is defined at
8660 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 """
9276 Initialise this Duration instance with the given parameters.
93 '''
77 """
9478 if not isinstance(months, Decimal):
9579 months = Decimal(str(months))
9680 if not isinstance(years, Decimal):
9781 years = Decimal(str(years))
9882 self.months = months
9983 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 )
10287
10388 def __getstate__(self):
10489 return self.__dict__
10792 self.__dict__.update(state)
10893
10994 def __getattr__(self, name):
110 '''
95 """
11196 Provide direct access to attributes of included timedelta instance.
112 '''
97 """
11398 return getattr(self.tdelta, name)
11499
115100 def __str__(self):
116 '''
101 """
117102 Return a string representation of this duration similar to timedelta.
118 '''
103 """
119104 params = []
120105 if self.years:
121 params.append('%d years' % self.years)
106 params.append("%d years" % self.years)
122107 if self.months:
123108 fmt = "%d months"
124109 if self.months <= 1:
125110 fmt = "%d month"
126111 params.append(fmt % self.months)
127112 params.append(str(self.tdelta))
128 return ', '.join(params)
113 return ", ".join(params)
129114
130115 def __repr__(self):
131 '''
116 """
132117 Return a string suitable for repr(x) calls.
133 '''
118 """
134119 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 )
138128
139129 def __hash__(self):
140 '''
130 """
141131 Return a hash of this instance so that it can be used in, for
142132 example, dicts and sets.
143 '''
133 """
144134 return hash((self.tdelta, self.months, self.years))
145135
146136 def __neg__(self):
154144 return negduration
155145
156146 def __add__(self, other):
157 '''
147 """
158148 Durations can be added with Duration, timedelta, date and datetime
159149 objects.
160 '''
150 """
161151 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 )
164155 newduration.tdelta = self.tdelta + other.tdelta
165156 return newduration
166157 try:
167158 # try anything that looks like a date or datetime
168159 # 'other' has attributes year, month, day
169160 # 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 )
174165 newmonth = other.month + self.months
175166 carry, newmonth = fquotmod(newmonth, 1, 13)
176167 newyear = other.year + self.years + carry
203194
204195 def __mul__(self, other):
205196 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)
209198 newduration.tdelta = self.tdelta * other
210199 return newduration
211200 return NotImplemented
213202 __rmul__ = __mul__
214203
215204 def __sub__(self, other):
216 '''
205 """
217206 It is possible to subtract Duration and timedelta objects from Duration
218207 objects.
219 '''
208 """
220209 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 )
223213 newduration.tdelta = self.tdelta - other.tdelta
224214 return newduration
225215 try:
233223 return NotImplemented
234224
235225 def __rsub__(self, other):
236 '''
226 """
237227 It is possible to subtract Duration objecs from date, datetime and
238228 timedelta objects.
239229
245235 the stdlib we can just do:
246236 return -self + other
247237 instead of all the current code
248 '''
238 """
249239 if isinstance(other, timedelta):
250240 tmpdur = Duration()
251241 tmpdur.tdelta = other
253243 try:
254244 # check if other behaves like a date/datetime object
255245 # 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 )
260250 newmonth = other.month - self.months
261251 carry, newmonth = fquotmod(newmonth, 1, 13)
262252 newyear = other.year - self.years + carry
275265 return NotImplemented
276266
277267 def __eq__(self, other):
278 '''
268 """
279269 If the years, month part and the timedelta part are both equal, then
280270 the two Durations are considered equal.
281 '''
271 """
282272 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:
286276 return True
287277 return False
288278 # check if other con be compared against timedelta object
292282 return False
293283
294284 def __ne__(self, other):
295 '''
285 """
296286 If the years, month part or the timedelta part is not equal, then
297287 the two Durations are considered not equal.
298 '''
288 """
299289 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:
303293 return True
304294 return False
305295 # check if other can be compared against timedelta object
309299 return True
310300
311301 def totimedelta(self, start=None, end=None):
312 '''
302 """
313303 Convert this duration into a timedelta object.
314304
315305 This method requires a start datetime or end datetimem, but raises
316306 an exception if both are given.
317 '''
307 """
318308 if start is None and end is None:
319309 raise ValueError("start or end required")
320310 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 """
271 This modules provides a method to parse an ISO 8601:2004 date string to a
282 python datetime.date instance.
293
304 It supports all basic, extended and expanded formats as described in the ISO
315 standard. The only limitations it has, are given by the Python datetime.date
326 implementation, which does not support dates before 0001-01-01.
33 '''
7 """
348 import re
359 from datetime import date, timedelta
3610
4519
4620
4721 def build_date_regexps(yeardigits=4, expanded=False):
48 '''
22 """
4923 Compile set of regular expressions to parse ISO dates. The expressions will
5024 be created only if they are not already in REGEX_CACHE.
5125
5529 ISO 8601 allows more than 4 digit years, on prior agreement, but then a +/-
5630 sign is required (expanded format). To support +/- sign for 4 digit years,
5731 the expanded parameter needs to be set to True.
58 '''
32 """
5933 if yeardigits != 4:
6034 expanded = True
6135 if (yeardigits, expanded) not in DATE_REGEX_CACHE:
6640 sign = 1
6741 else:
6842 sign = 0
43
44 def add_re(regex_text):
45 cache_entry.append(re.compile(r"\A" + regex_text + r"\Z"))
46
6947 # 1. complete dates:
7048 # 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 )
7453 # 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 )
7858 # 2. complete week dates:
7959 # 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 )
8364 # 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 )
8769 # 3. ordinal dates:
8870 # 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 )
9275 # 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 )
9680 # 4. week dates:
9781 # 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 )
10188 # 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 )
10593 # 5. month dates:
10694 # 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 )
110101 # 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 )
114106 # 6. year dates:
115107 # 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))
118109 # 7. century dates:
119110 # 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))
123112
124113 DATE_REGEX_CACHE[(yeardigits, expanded)] = cache_entry
125114 return DATE_REGEX_CACHE[(yeardigits, expanded)]
126115
127116
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 """
132119 Parse an ISO 8601 date string into a datetime.date object.
133120
134121 As the datetime.date implementation is limited to dates starting from
161148 @return: a datetime.date instance represented by datestring
162149 @raise ISO8601Error: if this function can not parse the datestring
163150 @raise ValueError: if datestring can not be represented by datetime.date
164 '''
151 """
165152 if yeardigits != 4:
166153 expanded = True
167154 isodates = build_date_regexps(yeardigits, expanded)
171158 groups = match.groupdict()
172159 # sign, century, year, month, week, day,
173160 # 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:
176163 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:
182169 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)
185172 else:
186173 days = 1
187174 # 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)
193181 else: # year date
194182 return ret.replace(month=defaultmonth, day=defaultday)
195183 # 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:
197185 day = defaultday
198186 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)
203192
204193
205194 def date_isoformat(tdate, format=DATE_EXT_COMPLETE, yeardigits=4):
206 '''
195 """
207196 Format date strings.
208197
209198 This method is just a wrapper around isodate.isostrf.strftime and uses
210199 Date-Extended-Complete as default format.
211 '''
200 """
212201 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 """
271 This module defines a method to parse an ISO 8601:2004 date time string.
282
293 For this job it uses the parse_date and parse_time methods defined in date
304 and time module.
31 '''
5 """
326 from datetime import datetime
337
348 from isodate.isostrf import strftime
3913
4014
4115 def parse_datetime(datetimestring):
42 '''
16 """
4317 Parses ISO 8601 date-times into datetime.datetime objects.
4418
4519 This function uses parse_date and parse_time to do the job, so it allows
4620 more combinations of date and time representations, than the actual
4721 ISO 8601:2004 standard allows.
48 '''
22 """
4923 try:
50 datestring, timestring = datetimestring.split('T')
24 datestring, timestring = datetimestring.split("T")
5125 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 )
5430 tmpdate = parse_date(datestring)
5531 tmptime = parse_time(timestring)
5632 return datetime.combine(tmpdate, tmptime)
5733
5834
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 """
6239 Format datetime strings.
6340
6441 This method is just a wrapper around isodate.isostrf.strftime and uses
6542 Extended-Complete as default format.
66 '''
43 """
6744 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 """
271 This module provides an ISO 8601:2004 duration parser.
282
293 It also provides a wrapper to strftime. This wrapper makes it easier to
304 format timedelta or Duration instances as ISO conforming strings.
31 '''
5 """
326 from datetime import timedelta
337 from decimal import Decimal
348 import re
35
36 from six import string_types
379
3810 from isodate.duration import Duration
3911 from isodate.isoerror import ISO8601Error
4921 r"(?P<days>[0-9]+([,.][0-9]+)?D)?"
5022 r"((?P<separator>T)(?P<hours>[0-9]+([,.][0-9]+)?H)?"
5123 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.
5427
5528
56 def parse_duration(datestring):
29 def parse_duration(datestring, as_timedelta_if_possible=True):
5730 """
5831 Parses an ISO 8601 durations into datetime.timedelta or Duration objects.
5932
8154 The alternative format does not support durations with years, months or
8255 days set to 0.
8356 """
84 if not isinstance(datestring, string_types):
57 if not isinstance(datestring, str):
8558 raise TypeError("Expecting a string %r" % datestring)
8659 match = ISO8601_PERIOD_REGEX.match(datestring)
8760 if not match:
8861 # try alternative format:
8962 if datestring.startswith("P"):
9063 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:
9275 # 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 )
10285 return ret
10386 raise ISO8601Error("Unable to parse duration string %r" % datestring)
10487 groups = match.groupdict()
10588 for key, val in groups.items():
106 if key not in ('separator', 'sign'):
89 if key not in ("separator", "sign"):
10790 if val is None:
10891 groups[key] = "0n"
10992 # 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(",", "."))
11295 else:
11396 # these values are passed into a timedelta object,
11497 # 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"] == "-":
121108 ret = timedelta(0) - ret
122109 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"] == "-":
128120 ret = Duration(0) - ret
129121 return ret
130122
131123
132124 def duration_isoformat(tduration, format=D_DEFAULT):
133 '''
125 """
134126 Format duration strings.
135127
136128 This method is just a wrapper around isodate.isostrf.strftime and uses
137129 P%P (D_DEFAULT) as default format.
138 '''
130 """
139131 # TODO: implement better decision for negative Durations.
140132 # 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 = "-"
147142 else:
148 ret = ''
143 ret = ""
149144 ret += strftime(tduration, format)
150145 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 """
271 This module defines all exception classes in the whole package.
28 '''
2 """
293
304
315 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 ##############################################################################
260 """
271 This module provides an alternative strftime method.
282
3913 from isodate.isotzinfo import tz_isoformat
4014
4115 # 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"
5428
5529 # 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"
6135
6236 # 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"
6640
6741 # 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
7648
7749 # 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
8456
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 }
10676
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 }
12091
12192
12293 def _strfduration(tdt, format, yeardigits=4):
123 '''
94 """
12495 this is the work method for timedelta and Duration instances.
12596
12697 see strftime for more details.
127 '''
98 """
99
128100 def repl(match):
129 '''
101 """
130102 lookup format command and return corresponding replacement.
131 '''
103 """
132104 if match.group(0) in STRF_D_MAP:
133105 return STRF_D_MAP[match.group(0)](tdt, yeardigits)
134 elif match.group(0) == '%P':
106 elif match.group(0) == "%P":
135107 ret = []
136108 if isinstance(tdt, Duration):
137109 if tdt.years:
138 ret.append('%sY' % abs(tdt.years))
110 ret.append("%sY" % abs(tdt.years))
139111 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 )
143116 seconds, usecs = divmod(usecs, 1000000)
144117 minutes, seconds = divmod(seconds, 60)
145118 hours, minutes = divmod(minutes, 60)
146119 days, hours = divmod(hours, 24)
147120 if days:
148 ret.append('%sD' % days)
121 ret.append("%sD" % days)
149122 if hours or minutes or seconds or usecs:
150 ret.append('T')
123 ret.append("T")
151124 if hours:
152 ret.append('%sH' % hours)
125 ret.append("%sH" % hours)
153126 if minutes:
154 ret.append('%sM' % minutes)
127 ret.append("%sM" % minutes)
155128 if seconds or usecs:
156129 if usecs:
157 ret.append(("%d.%06d" % (seconds, usecs)).rstrip('0'))
130 ret.append(("%d.%06d" % (seconds, usecs)).rstrip("0"))
158131 else:
159132 ret.append("%d" % seconds)
160 ret.append('S')
133 ret.append("S")
161134 # 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"
165138 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)
168141
169142
170143 def _strfdt(tdt, format, yeardigits=4):
171 '''
144 """
172145 this is the work method for time and date instances.
173146
174147 see strftime for more details.
175 '''
148 """
149
176150 def repl(match):
177 '''
151 """
178152 lookup format command and return corresponding replacement.
179 '''
153 """
180154 if match.group(0) in STRF_DT_MAP:
181155 return STRF_DT_MAP[match.group(0)](tdt, yeardigits)
182156 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)
185159
186160
187161 def strftime(tdt, format, yeardigits=4):
188 '''Directive Meaning Notes
162 """Directive Meaning Notes
189163 %d Day of the month as a decimal number [01,31].
190164 %f Microsecond as a decimal number [0,999999], zero-padded
191165 on the left (1)
207181 %p ISO8601 duration format in weeks.
208182 %% A literal '%' character.
209183
210 '''
184 """
211185 if isinstance(tdt, (timedelta, Duration)):
212186 return _strfduration(tdt, format, yeardigits)
213187 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 """
271 This modules provides a method to parse an ISO 8601:2004 time string to a
282 Python datetime.time instance.
293
304 It supports all basic and extended formats including time zone specifications
315 as described in the ISO standard.
32 '''
6 """
337 import re
34 from decimal import Decimal
8 from decimal import Decimal, ROUND_FLOOR
359 from datetime import time
3610
3711 from isodate.isostrf import strftime, TIME_EXT_COMPLETE, TZ_EXT
4317
4418
4519 def build_time_regexps():
46 '''
20 """
4721 Build regular expressions to parse ISO time string.
4822
4923 The regular expressions are compiled and stored in TIME_REGEX_CACHE
5024 for later reuse.
51 '''
25 """
5226 if not TIME_REGEX_CACHE:
5327 # ISO 8601 time representations allow decimal fractions on least
5428 # significant time component. Command and Full Stop are both valid
6640 # +-hhmm
6741 # +-hh =>
6842 # 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
6946 # 1. complete time:
7047 # 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 )
7554 # 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 )
8061 # 2. reduced accuracy:
8162 # 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]+)?)")
8564 # 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]+)?)")
8966 # 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]+)?)")
9268 return TIME_REGEX_CACHE
9369
9470
9571 def parse_time(timestring):
96 '''
72 """
9773 Parses ISO 8601 times into datetime.time objects.
9874
9975 Following ISO 8601 formats are supported:
10985 +-hhmm basic hours and minutes
11086 +-hh:mm extended hours and minutes
11187 +-hh hours
112 '''
88 """
11389 isotimes = build_time_regexps()
11490 for pattern in isotimes:
11591 match = pattern.match(timestring)
11793 groups = match.groupdict()
11894 for key, value in groups.items():
11995 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 )
127107 microsecond = (second - int(second)) * int(1e6)
128108 # int(...) ... no rounding
129109 # 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 )
136122 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 )
139130 else:
140131 microsecond, second, minute = 0, 0, 0
141 hour = Decimal(groups['hour'])
132 hour = Decimal(groups["hour"])
142133 minute = (hour - int(hour)) * 60
143134 second = (minute - int(minute)) * 60
144135 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)
148144
149145
150146 def time_isoformat(ttime, format=TIME_EXT_COMPLETE + TZ_EXT):
151 '''
147 """
152148 Format time strings.
153149
154150 This method is just a wrapper around isodate.isostrf.strftime and uses
155151 Time-Extended-Complete with extended time zone as default format.
156 '''
152 """
157153 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 """
271 This module provides an ISO 8601:2004 time zone info parser.
282
293 It offers a function to parse the time zone offset as specified by ISO 8601.
30 '''
4 """
315 import re
326
337 from isodate.isoerror import ISO8601Error
348 from isodate.tzinfo import UTC, FixedOffset, ZERO
359
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 )
3813
3914 TZ_RE = re.compile(TZ_REGEX)
4015
4116
42 def build_tzinfo(tzname, tzsign='+', tzhour=0, tzmin=0):
43 '''
17 def build_tzinfo(tzname, tzsign="+", tzhour=0, tzmin=0):
18 """
4419 create a tzinfo instance according to given parameters.
4520
4621 tzname:
4722 'Z' ... return UTC
4823 '' | None ... return None
4924 other ... return FixedOffset
50 '''
51 if tzname is None or tzname == '':
25 """
26 if tzname is None or tzname == "":
5227 return None
53 if tzname == 'Z':
28 if tzname == "Z":
5429 return UTC
55 tzsign = ((tzsign == '-') and -1) or 1
30 tzsign = ((tzsign == "-") and -1) or 1
5631 return FixedOffset(tzsign * tzhour, tzsign * tzmin, tzname)
5732
5833
5934 def parse_tzinfo(tzstring):
60 '''
35 """
6136 Parses ISO 8601 time zone designators to tzinfo objecs.
6237
6338 A time zone designator can be in the following format:
6641 +-hhmm basic hours and minutes
6742 +-hh:mm extended hours and minutes
6843 +-hh hours
69 '''
44 """
7045 match = TZ_RE.match(tzstring)
7146 if match:
7247 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)
7755
7856
79 def tz_isoformat(dt, format='%Z'):
80 '''
57 def tz_isoformat(dt, format="%Z"):
58 """
8159 return time zone offset ISO 8601 formatted.
8260 The various ISO formats can be chosen with the format parameter.
8361
8866 %h ... +-HH
8967 %z ... +-HHMM
9068 %Z ... +-HH:MM
91 '''
69 """
9270 tzinfo = dt.tzinfo
9371 if (tzinfo is None) or (tzinfo.utcoffset(dt) is None):
94 return ''
72 return ""
9573 if tzinfo.utcoffset(dt) == ZERO and tzinfo.dst(dt) == ZERO:
96 return 'Z'
74 return "Z"
9775 tdelta = tzinfo.utcoffset(dt)
9876 seconds = tdelta.days * 24 * 60 * 60 + tdelta.seconds
99 sign = ((seconds < 0) and '-') or '+'
77 sign = ((seconds < 0) and "-") or "+"
10078 seconds = abs(seconds)
10179 minutes, seconds = divmod(seconds, 60)
10280 hours, minutes = divmod(minutes, 60)
10381 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)
11189 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 """
271 Collect all test suites into one TestSuite instance.
28 '''
2 """
293
304 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 )
3314
3415
3516 def test_suite():
36 '''
17 """
3718 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 )
4732
4833
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 """
271 Test cases for the isodate module.
28 '''
2 """
293 import unittest
304 from datetime import date
315 from isodate import parse_date, ISO8601Error, date_isoformat
4014 # result from the parse_date method. A result of None means an ISO8601Error
4115 # is expected. The test cases are grouped into dates with 4 digit years
4216 # 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 }
6949
7050
7151 def create_testcase(yeardigits, datestring, expectation, format):
72 '''
52 """
7353 Create a TestCase class for a specific test.
7454
7555 This allows having a separate TestCase for each test tuple from the
7656 TEST_CASES list, so that a failed test won't stop other tests.
77 '''
57 """
7858
7959 class TestDate(unittest.TestCase):
80 '''
60 """
8161 A test case template to parse an ISO date string into a date
8262 object.
83 '''
63 """
8464
8565 def test_parse(self):
86 '''
66 """
8767 Parse an ISO date string and compare it to the expected value.
88 '''
68 """
8969 if expectation is None:
90 self.assertRaises(ISO8601Error, parse_date, datestring,
91 yeardigits)
70 self.assertRaises(ISO8601Error, parse_date, datestring, yeardigits)
9271 else:
9372 result = parse_date(datestring, yeardigits)
9473 self.assertEqual(result, expectation)
9574
9675 def test_format(self):
97 '''
76 """
9877 Take date object and create ISO string from it.
9978 This is the reverse test to test_parse.
100 '''
79 """
10180 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 )
10584 else:
106 self.assertEqual(date_isoformat(expectation, format,
107 yeardigits),
108 datestring)
85 self.assertEqual(
86 date_isoformat(expectation, format, yeardigits), datestring
87 )
10988
11089 return unittest.TestLoader().loadTestsFromTestCase(TestDate)
11190
11291
11392 def test_suite():
114 '''
93 """
11594 Construct a TestSuite instance for all test cases.
116 '''
95 """
11796 suite = unittest.TestSuite()
11897 for yeardigits, tests in TEST_CASES.items():
11998 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))
122100 return suite
123101
124102
127105 return test_suite()
128106
129107
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 """
271 Test cases for the isodatetime module.
28 '''
2 """
293 import unittest
304 from datetime import datetime
315
4014 # the following list contains tuples of ISO datetime strings and the expected
4115 # result from the parse_datetime method. A result of None means an ISO8601Error
4216 # 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 ]
99124
100125
101126 def create_testcase(datetimestring, expectation, format, output):
107132 """
108133
109134 class TestDateTime(unittest.TestCase):
110 '''
135 """
111136 A test case template to parse an ISO datetime string into a
112137 datetime object.
113 '''
138 """
114139
115140 def test_parse(self):
116 '''
141 """
117142 Parse an ISO datetime string and compare it to the expected value.
118 '''
143 """
119144 if expectation is None:
120145 self.assertRaises(ISO8601Error, parse_datetime, datetimestring)
121146 else:
122147 self.assertEqual(parse_datetime(datetimestring), expectation)
123148
124149 def test_format(self):
125 '''
150 """
126151 Take datetime object and create ISO string from it.
127152 This is the reverse test to test_parse.
128 '''
153 """
129154 if expectation is None:
130 self.assertRaises(AttributeError,
131 datetime_isoformat, expectation, format)
155 self.assertRaises(
156 AttributeError, datetime_isoformat, expectation, format
157 )
132158 else:
133 self.assertEqual(datetime_isoformat(expectation, format),
134 output)
159 self.assertEqual(datetime_isoformat(expectation, format), output)
135160
136161 return unittest.TestLoader().loadTestsFromTestCase(TestDateTime)
137162
138163
139164 def test_suite():
140 '''
165 """
141166 Construct a TestSuite instance for all test cases.
142 '''
167 """
143168 suite = unittest.TestSuite()
144169 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))
147171 return suite
148172
149173
152176 return test_suite()
153177
154178
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 """
271 Test cases for the isoduration module.
28 '''
2 """
293 import unittest
304 import operator
315 from datetime import timedelta, date, datetime
3610 # the following list contains tuples of ISO duration strings and the expected
3711 # result from the parse_duration method. A result of None means an ISO8601Error
3812 # 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 }
8047
8148 # d1 d2 '+', '-', '>'
8249 # A list of test cases to test addition and subtraction between datetime and
8451 # each tuple contains 2 duration strings, and a result string for addition and
8552 # one for subtraction. The last value says, if the first duration is greater
8653 # 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 )
9986
10087
10188 # A list of test cases to test addition and subtraction of date/datetime
10289 # and Duration objects. They are tested against the results of an
10390 # 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 )
134134
135135 # A list of test cases of additon of date/datetime and Duration. The results
136136 # are compared against a given expected result.
137137 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)),
225191 # (date(2000, 1, 1),
226192 # Duration(years=1.5),
227193 # date(2001, 6, 1)),
228194 # (date(2000, 1, 1),
229195 # Duration(years=1, months=1.5),
230196 # date(2001, 2, 14)),
231 )
197 )
232198
233199 # A list of test cases of multiplications of durations
234200 # are compared against a given expected result.
235201 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 )
257210
258211
259212 class DurationTest(unittest.TestCase):
260 '''
213 """
261214 This class tests various other aspects of the isoduration module,
262215 which are not covered with the test cases listed above.
263 '''
216 """
264217
265218 def test_associative(self):
266 '''
219 """
267220 Adding 2 durations to a date is not associative.
268 '''
221 """
269222 days1 = Duration(days=1)
270223 months1 = Duration(months=1)
271224 start = date(2000, 3, 30)
274227 self.assertNotEqual(res1, res2)
275228
276229 def test_typeerror(self):
277 '''
230 """
278231 Test if TypError is raised with certain parameters.
279 '''
232 """
280233 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 )
300266
301267 def test_parseerror(self):
302 '''
268 """
303269 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")
306272
307273 def test_repr(self):
308 '''
274 """
309275 Test __repr__ and __str__ for Duration objects.
310 '''
276 """
311277 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 )
315282 dur = Duration(months=0)
316 self.assertEqual('0:00:00', str(dur))
283 self.assertEqual("0:00:00", str(dur))
317284 dur = Duration(months=1)
318 self.assertEqual('1 month, 0:00:00', str(dur))
285 self.assertEqual("1 month, 0:00:00", str(dur))
319286
320287 def test_hash(self):
321 '''
288 """
322289 Test __hash__ for Duration objects.
323 '''
290 """
324291 dur1 = Duration(10, 10, years=10, months=10)
325292 dur2 = Duration(9, 9, years=9, months=9)
326293 dur3 = Duration(10, 10, years=10, months=10)
335302 self.assertEqual(len(durSet), 2)
336303
337304 def test_neg(self):
338 '''
305 """
339306 Test __neg__ for Duration objects.
340 '''
307 """
341308 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))
344310 self.assertEqual(-Duration(years=1, months=1), Duration(months=-13))
345311 self.assertNotEqual(-Duration(years=1), timedelta(days=-365))
346312 self.assertNotEqual(-timedelta(days=365), Duration(years=-1))
349315 # self.assertNotEqual(-timedelta(days=10), -Duration(days=10))
350316
351317 def test_format(self):
352 '''
318 """
353319 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")
370333
371334 def test_equal(self):
372 '''
335 """
373336 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))
377339 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))
380341 self.assertNotEqual(Duration(years=1, months=1), Duration(months=14))
381342 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))
384344 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))
387346 self.assertTrue(Duration(years=1, months=1) != Duration(months=14))
388347 self.assertTrue(Duration(years=1) != timedelta(days=365))
389348 self.assertEqual(Duration(days=1), timedelta(days=1))
392351 # self.assertNotEqual(timedelta(days=1), Duration(days=1))
393352
394353 def test_totimedelta(self):
395 '''
354 """
396355 Test conversion form Duration to timedelta.
397 '''
356 """
398357 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))
401359 # 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))
404361 dur = Duration(months=2)
405362 # march is longer than february, but april is shorter than
406363 # march (cause only one day difference compared to 2)
418375 """
419376
420377 class TestParseDuration(unittest.TestCase):
421 '''
378 """
422379 A test case template to parse an ISO duration string into a
423380 timedelta or Duration object.
424 '''
381 """
425382
426383 def test_parse(self):
427 '''
384 """
428385 Parse an ISO duration string and compare it to the expected value.
429 '''
386 """
430387 result = parse_duration(durationstring)
431388 self.assertEqual(result, expectation)
432389
433390 def test_format(self):
434 '''
391 """
435392 Take duration/timedelta object and create ISO string from it.
436393 This is the reverse test to test_parse.
437 '''
394 """
438395 if altstr:
439 self.assertEqual(duration_isoformat(expectation, format),
440 altstr)
396 self.assertEqual(duration_isoformat(expectation, format), altstr)
441397 else:
442398 # if durationstring == '-P2W':
443399 # import pdb; pdb.set_trace()
444 self.assertEqual(duration_isoformat(expectation, format),
445 durationstring)
400 self.assertEqual(
401 duration_isoformat(expectation, format), durationstring
402 )
446403
447404 return unittest.TestLoader().loadTestsFromTestCase(TestParseDuration)
448405
461418 ressub = parse_duration(ressub)
462419
463420 class TestMathDuration(unittest.TestCase):
464 '''
421 """
465422 A test case template test addition, subtraction and >
466423 operators for Duration objects.
467 '''
424 """
468425
469426 def test_add(self):
470 '''
427 """
471428 Test operator + (__add__, __radd__)
472 '''
429 """
473430 self.assertEqual(dur1 + dur2, resadd)
474431
475432 def test_sub(self):
476 '''
433 """
477434 Test operator - (__sub__, __rsub__)
478 '''
435 """
479436 self.assertEqual(dur1 - dur2, ressub)
480437
481438 def test_ge(self):
482 '''
439 """
483440 Test operator > and <
484 '''
441 """
442
485443 def dogetest():
486 ''' Test greater than.'''
444 """Test greater than."""
487445 return dur1 > dur2
488446
489447 def doletest():
490 ''' Test less than.'''
448 """Test less than."""
491449 return dur1 < dur2
450
492451 if resge is None:
493452 self.assertRaises(TypeError, dogetest)
494453 self.assertRaises(TypeError, doletest)
508467 """
509468
510469 class TestDateCalc(unittest.TestCase):
511 '''
470 """
512471 A test case template test addition, subtraction
513472 operators for Duration objects.
514 '''
473 """
515474
516475 def test_add(self):
517 '''
476 """
518477 Test operator +.
519 '''
478 """
520479 self.assertEqual(start + tdelta, start + duration)
521480
522481 def test_sub(self):
523 '''
482 """
524483 Test operator -.
525 '''
484 """
526485 self.assertEqual(start - tdelta, start - duration)
527486
528487 return unittest.TestLoader().loadTestsFromTestCase(TestDateCalc)
537496 """
538497
539498 class TestDateCalc(unittest.TestCase):
540 '''
499 """
541500 A test case template test addition operators for Duration objects.
542 '''
501 """
543502
544503 def test_calc(self):
545 '''
504 """
546505 Test operator +.
547 '''
506 """
548507 if expectation is None:
549508 self.assertRaises(ValueError, operator.add, start, duration)
550509 else:
562521 """
563522
564523 class TestDateMul(unittest.TestCase):
565 '''
524 """
566525 A test case template test addition operators for Duration objects.
567 '''
526 """
568527
569528 def test_mul(self):
570 '''
529 """
571530 Test operator *.
572 '''
531 """
573532 self.assertEqual(operand1 * operand2, expectation)
574533
575534 return unittest.TestLoader().loadTestsFromTestCase(TestDateMul)
576535
577536
578537 def test_suite():
579 '''
538 """
580539 Return a test suite containing all test defined above.
581 '''
540 """
582541 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))
587544 for testdata in MATH_TEST_CASES:
588545 suite.addTest(create_mathtestcase(*testdata))
589546 for testdata in DATE_TEST_CASES:
601558 return test_suite()
602559
603560
604 if __name__ == '__main__':
605 unittest.main(defaultTest='test_suite')
561 if __name__ == "__main__":
562 unittest.main(defaultTest="test_suite")
00 import unittest
11
2 from six.moves import cPickle as pickle
2 import pickle
33
44 import isodate
55
66
77 class TestPickle(unittest.TestCase):
8 '''
8 """
99 A test case template to parse an ISO datetime string into a
1010 datetime object.
11 '''
11 """
1212
1313 def test_pickle_datetime(self):
14 '''
14 """
1515 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")
1818 for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
1919 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)
2221
2322 def test_pickle_duration(self):
24 '''
23 """
2524 Pickle / unpickle duration objects.
26 '''
25 """
2726 from isodate.duration import Duration
27
2828 dur = Duration()
2929 failed = []
3030 for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
3434 raise Exception("not equal")
3535 except Exception as e:
3636 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))
3938
4039 def test_pickle_utc(self):
41 '''
40 """
4241 isodate.UTC objects remain the same after pickling.
43 '''
42 """
4443 self.assertTrue(isodate.UTC is pickle.loads(pickle.dumps(isodate.UTC)))
4544
4645
4746 def test_suite():
48 '''
47 """
4948 Construct a TestSuite instance for all test cases.
50 '''
49 """
5150 suite = unittest.TestSuite()
5251 suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestPickle))
5352 return suite
5857 return test_suite()
5958
6059
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 """
271 Test cases for the isodate module.
28 '''
2 """
293 import unittest
304 import time
315 from datetime import datetime, timedelta
359 from isodate import tzinfo
3610
3711
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 )
5136
5237
5338 def create_testcase(dt, format, expectation):
5944 """
6045
6146 class TestDate(unittest.TestCase):
62 '''
47 """
6348 A test case template to test ISO date formatting.
64 '''
49 """
6550
6651 # local time zone mock function
6752 def localtime_mock(self, secs):
6954 mock time.localtime so that it always returns a time_struct with
7055 tm_idst=1
7156 """
72 tt = self.ORIG['localtime'](secs)
57 tt = self.ORIG["localtime"](secs)
7358 # befor 2000 everything is dst, after 2000 no dst.
7459 if tt.tm_year < 2000:
7560 dst = 1
7661 else:
7762 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 )
8174 return time.struct_time(tt)
8275
8376 def setUp(self):
8477 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
8982 # ovveride all saved values with fixtures.
9083 # calculate LOCAL TZ offset, so that this test runs in
9184 # every time zone
9689
9790 def tearDown(self):
9891 # 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"]
10396
10497 def test_format(self):
105 '''
98 """
10699 Take date object and create ISO string from it.
107100 This is the reverse test to test_parse.
108 '''
101 """
109102 if expectation is None:
110 self.assertRaises(AttributeError,
111 strftime(dt, format))
103 self.assertRaises(AttributeError, strftime(dt, format))
112104 else:
113 self.assertEqual(strftime(dt, format),
114 expectation)
105 self.assertEqual(strftime(dt, format), expectation)
115106
116107 return unittest.TestLoader().loadTestsFromTestCase(TestDate)
117108
118109
119110 def test_suite():
120 '''
111 """
121112 Construct a TestSuite instance for all test cases.
122 '''
113 """
123114 suite = unittest.TestSuite()
124115 for dt, format, expectation in TEST_CASES:
125116 suite.addTest(create_testcase(dt, format, expectation))
131122 return test_suite()
132123
133124
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 """
271 Test cases for the isotime module.
28 '''
2 """
293 import unittest
304 from datetime import time
315
3812 # the following list contains tuples of ISO time strings and the expected
3913 # result from the parse_time method. A result of None means an ISO8601Error
4014 # 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 ]
9292
9393
9494 def create_testcase(timestring, expectation, format):
100100 """
101101
102102 class TestTime(unittest.TestCase):
103 '''
103 """
104104 A test case template to parse an ISO time string into a time
105105 object.
106 '''
106 """
107107
108108 def test_parse(self):
109 '''
109 """
110110 Parse an ISO time string and compare it to the expected value.
111 '''
111 """
112112 if expectation is None:
113113 self.assertRaises(ISO8601Error, parse_time, timestring)
114114 else:
116116 self.assertEqual(result, expectation)
117117
118118 def test_format(self):
119 '''
119 """
120120 Take time object and create ISO string from it.
121121 This is the reverse test to test_parse.
122 '''
122 """
123123 if expectation is None:
124 self.assertRaises(AttributeError,
125 time_isoformat, expectation, format)
124 self.assertRaises(AttributeError, time_isoformat, expectation, format)
126125 elif format is not None:
127 self.assertEqual(time_isoformat(expectation, format),
128 timestring)
126 self.assertEqual(time_isoformat(expectation, format), timestring)
129127
130128 return unittest.TestLoader().loadTestsFromTestCase(TestTime)
131129
132130
133131 def test_suite():
134 '''
132 """
135133 Construct a TestSuite instance for all test cases.
136 '''
134 """
137135 suite = unittest.TestSuite()
138136 for timestring, expectation, format in TEST_CASES:
139137 suite.addTest(create_testcase(timestring, expectation, format))
145143 return test_suite()
146144
147145
148 if __name__ == '__main__':
149 unittest.main(defaultTest='test_suite')
146 if __name__ == "__main__":
147 unittest.main(defaultTest="test_suite")
0 '''
0 """
11 This module provides some datetime.tzinfo implementations.
22
33 All those classes are taken from the Python documentation.
4 '''
4 """
55 from datetime import timedelta, tzinfo
66 import time
77
1010
1111
1212 class Utc(tzinfo):
13 '''UTC
13 """UTC
1414
1515 Universal time coordinated time zone.
16 '''
16 """
1717
1818 def utcoffset(self, dt):
19 '''
19 """
2020 Return offset from UTC in minutes east of UTC, which is ZERO for UTC.
21 '''
21 """
2222 return ZERO
2323
2424 def tzname(self, dt):
25 '''
25 """
2626 Return the time zone name corresponding to the datetime object dt,
2727 as a string.
28 '''
28 """
2929 return "UTC"
3030
3131 def dst(self, dt):
32 '''
32 """
3333 Return the daylight saving time (DST) adjustment, in minutes east
3434 of UTC.
35 '''
35 """
3636 return ZERO
3737
3838 def __reduce__(self):
39 '''
39 """
4040 When unpickling a Utc object, return the default instance below, UTC.
41 '''
41 """
4242 return _Utc, ()
4343
4444
4747
4848
4949 def _Utc():
50 '''
50 """
5151 Helper function for unpickling a Utc object.
52 '''
52 """
5353 return UTC
5454
5555
5656 class FixedOffset(tzinfo):
57 '''
57 """
5858 A class building tzinfo objects for fixed-offset time zones.
5959
6060 Note that FixedOffset(0, 0, "UTC") or FixedOffset() is a different way to
6161 build a UTC tzinfo object.
62 '''
62 """
6363
6464 def __init__(self, offset_hours=0, offset_minutes=0, name="UTC"):
65 '''
65 """
6666 Initialise an instance with time offset and name.
6767 The time offset should be positive for time zones east of UTC
6868 and negate for time zones west of UTC.
69 '''
69 """
7070 self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes)
7171 self.__name = name
7272
7373 def utcoffset(self, dt):
74 '''
74 """
7575 Return offset from UTC in minutes of UTC.
76 '''
76 """
7777 return self.__offset
7878
7979 def tzname(self, dt):
80 '''
80 """
8181 Return the time zone name corresponding to the datetime object dt, as a
8282 string.
83 '''
83 """
8484 return self.__name
8585
8686 def dst(self, dt):
87 '''
87 """
8888 Return the daylight saving time (DST) adjustment, in minutes east of
8989 UTC.
90 '''
90 """
9191 return ZERO
9292
9393 def __repr__(self):
94 '''
94 """
9595 Return nicely formatted repr string.
96 '''
96 """
9797 return "<FixedOffset %r>" % self.__name
9898
9999
116116 """
117117
118118 def utcoffset(self, dt):
119 '''
119 """
120120 Return offset from UTC in minutes of UTC.
121 '''
121 """
122122 if self._isdst(dt):
123123 return DSTOFFSET
124124 else:
125125 return STDOFFSET
126126
127127 def dst(self, dt):
128 '''
128 """
129129 Return daylight saving offset.
130 '''
130 """
131131 if self._isdst(dt):
132132 return DSTDIFF
133133 else:
134134 return ZERO
135135
136136 def tzname(self, dt):
137 '''
137 """
138138 Return the time zone name corresponding to the datetime object dt, as a
139139 string.
140 '''
140 """
141141 return time.tzname[self._isdst(dt)]
142142
143143 def _isdst(self, dt):
144 '''
144 """
145145 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 )
150158 stamp = time.mktime(tt)
151159 tt = time.localtime(stamp)
152160 return tt.tm_isdst > 0
00 Metadata-Version: 2.1
11 Name: isodate
2 Version: 0.6.1
2 Version: 0.7.0.dev0
33 Summary: An ISO 8601 date/time/duration parser and formatter
44 Home-page: https://github.com/gweis/isodate/
55 Author: Gerhard Weis
66 Author-email: gerhard.weis@proclos.com
7 License: BSD
7 License: BSD-3-Clause
88 Platform: UNKNOWN
99 Classifier: Development Status :: 4 - Beta
1010 Classifier: Intended Audience :: Developers
1111 Classifier: License :: OSI Approved :: BSD License
1212 Classifier: Operating System :: OS Independent
1313 Classifier: Programming Language :: Python
14 Classifier: Programming Language :: Python :: 2
15 Classifier: Programming Language :: Python :: 2.7
1614 Classifier: Programming Language :: Python :: 3
17 Classifier: Programming Language :: Python :: 3.6
1815 Classifier: Programming Language :: Python :: 3.7
1916 Classifier: Programming Language :: Python :: 3.8
2017 Classifier: Programming Language :: Python :: 3.9
2219 Classifier: Programming Language :: Python :: Implementation :: PyPy
2320 Classifier: Topic :: Internet
2421 Classifier: Topic :: Software Development :: Libraries :: Python Modules
22 License-File: LICENSE
2523
2624
2725 ISO 8601 date/time parser
6361 Documentation
6462 -------------
6563
66 Currently there are four parsing methods available.
64 The following parsing methods are available.
6765 * parse_time:
6866 parses an ISO 8601 time string into a *time* object
6967 * parse_date:
108106 prior 1900. This method also understands how to format *datetime* and
109107 *Duration* instances.
110108
111 Installation:
112 -------------
109 Installation
110 ------------
113111
114112 This module can easily be installed with Python standard installation methods.
115113
116114 Either use *python setup.py install* or in case you have *setuptools* or
117115 *distribute* available, you can also use *easy_install*.
118116
119 Limitations:
120 ------------
117 Limitations
118 -----------
121119
122120 * The parser accepts several date/time representation which should be invalid
123121 according to ISO 8601 standard.
129127 1901-01-01.
130128 3. negative *Duration* and *timedelta* value are not fully supported yet.
131129
132 Further information:
133 --------------------
130 Further information
131 -------------------
134132
135133 The doc strings and unit tests should provide rather detailed information about
136134 the methods and their limitations.
143141 CHANGES
144142 =======
145143
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
146153 0.6.1 (2021-12-13)
147154 ------------------
148155
149 - support python 3.10 ()
156 - support python 3.10 (Hugo van Kemenade)
150157 - last version to support py 2.7
151158
152159
00 CHANGES.txt
1 LICENSE
12 MANIFEST.in
23 README.rst
34 TODO.txt
1617 src/isodate.egg-info/PKG-INFO
1718 src/isodate.egg-info/SOURCES.txt
1819 src/isodate.egg-info/dependency_links.txt
19 src/isodate.egg-info/requires.txt
2020 src/isodate.egg-info/top_level.txt
2121 src/isodate/tests/__init__.py
2222 src/isodate/tests/test_date.py
+0
-1
src/isodate.egg-info/requires.txt less more
0 six