diff options
author | Gerhard Weis <gweis@gmx.at> | 2010-10-13 09:32:20 +1000 |
---|---|---|
committer | Gerhard Weis <gweis@gmx.at> | 2010-10-13 09:32:20 +1000 |
commit | bf641c3e00cece5673b7e96ded2d4d4d72b804aa (patch) | |
tree | 0b966befb1856cd12e476d9e0bdf403aa925d861 /src | |
parent | de8b4fcb5b87c03ee175fa7649b6bbb4cdee20a5 (diff) | |
download | isodate-bf641c3e00cece5673b7e96ded2d4d4d72b804aa.tar.gz |
* fixed precision problem with microseconds
Diffstat (limited to 'src')
-rw-r--r-- | src/isodate/isotime.py | 55 | ||||
-rw-r--r-- | src/tests/test_time.py | 63 |
2 files changed, 67 insertions, 51 deletions
diff --git a/src/isodate/isotime.py b/src/isodate/isotime.py index c677f74..f0003d6 100644 --- a/src/isodate/isotime.py +++ b/src/isodate/isotime.py @@ -14,11 +14,11 @@ # may be used to endorse or promote products derived from this software # without specific prior written permission. # -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN @@ -29,10 +29,10 @@ This modules provides a method to parse an ISO 8601:2004 time string to a Python datetime.time instance. It supports all basic and extended formats including time zone specifications -as described in the ISO standard. +as described in the ISO standard. ''' import re -import math +from decimal import Decimal from datetime import time from isodate.isostrf import strftime, TIME_EXT_COMPLETE, TZ_EXT @@ -42,10 +42,11 @@ from isodate.isotzinfo import TZ_REGEX, build_tzinfo TIME_REGEX_CACHE = [] # used to cache regular expressions to parse ISO time strings. + def build_time_regexps(): ''' Build regular expressions to parse ISO time string. - + The regular expressions are compiled and stored in TIME_REGEX_CACHE for later reuse. ''' @@ -91,10 +92,11 @@ def build_time_regexps(): + TZ_REGEX)) return TIME_REGEX_CACHE + def parse_time(timestring): ''' Parses ISO 8601 times into datetime.time objects. - + Following ISO 8601 formats are supported: (as decimal separator a ',' or a '.' is allowed) hhmmss.ssTZD basic complete time @@ -117,34 +119,37 @@ def parse_time(timestring): for key, value in groups.items(): if value is not None: groups[key] = value.replace(',', '.') - tzinfo = build_tzinfo(groups['tzname'], groups['tzsign'], - int(groups['tzhour'] or 0), + tzinfo = build_tzinfo(groups['tzname'], groups['tzsign'], + int(groups['tzhour'] or 0), int(groups['tzmin'] or 0)) if 'second' in groups: - frac, second = math.modf(float(groups['second'])) - microsecond = frac * 1e6 - return time(int(groups['hour']), int(groups['minute']), - int(second), int(microsecond), tzinfo) + second = Decimal(groups['second']) + microsecond = (second - int(second)) * long(1e6) + # int(...) ... no rounding + # to_integral() ... rounding + return time(int(groups['hour']), int(groups['minute']), + int(second), microsecond.to_integral(), tzinfo) if 'minute' in groups: - frac, minute = math.modf(float(groups['minute'])) - frac, second = math.modf(frac * 60.0) - microsecond = frac * 1e6 - return time(int(groups['hour']), int(minute), int(second), - int(microsecond), tzinfo) + minute = Decimal(groups['minute']) + second = (minute - int(minute)) * 60 + microsecond = (second - int(second)) * long(1e6) + return time(int(groups['hour']), int(minute), int(second), + microsecond.to_integral(), tzinfo) else: microsecond, second, minute = 0, 0, 0 - frac, hour = math.modf(float(groups['hour'])) - frac, minute = math.modf(frac * 60.0) - frac, second = math.modf(frac * 60.0) - microsecond = frac * 1e6 - return time(int(hour), int(minute), int(second), int(microsecond), - tzinfo) + hour = Decimal(groups['hour']) + minute = (hour - int(hour)) * 60 + second = (minute - int(minute)) * 60 + microsecond = (second - int(second)) * long(1e6) + return time(int(hour), int(minute), int(second), + microsecond.to_integral(), tzinfo) raise ISO8601Error('Unrecognised ISO 8601 time format: %r' % timestring) + def time_isoformat(ttime, format=TIME_EXT_COMPLETE + TZ_EXT): ''' - Format time strings. - + Format time strings. + This method is just a wrapper around isodate.isostrf.strftime and uses Time-Extended-Complete with extended time zone as default format. ''' diff --git a/src/tests/test_time.py b/src/tests/test_time.py index 062995b..d71a3e2 100644 --- a/src/tests/test_time.py +++ b/src/tests/test_time.py @@ -14,11 +14,11 @@ # may be used to endorse or promote products derived from this software # without specific prior written permission. # -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN @@ -46,51 +46,61 @@ TEST_CASES = [('232050', time(23, 20, 50), TIME_BAS_COMPLETE + TZ_BAS), ('23', time(23), TIME_HOUR), ('232050,5', time(23, 20, 50, 500000), None), ('23:20:50.5', time(23, 20, 50, 500000), None), + # test precision + ('15:33:42.123456', time(15, 33, 42, 123456), None), + ('15:33:42.1234564', time(15, 33, 42, 123456), None), + ('15:33:42.1234557', time(15, 33, 42, 123456), None), ('2320,8', time(23, 20, 48), None), ('23:20,8', time(23, 20, 48), None), ('23,3', time(23, 18), None), - ('232030Z', time(23, 20, 30, tzinfo=UTC), TIME_BAS_COMPLETE + TZ_BAS), + ('232030Z', time(23, 20, 30, tzinfo=UTC), + TIME_BAS_COMPLETE + TZ_BAS), ('2320Z', time(23, 20, tzinfo=UTC), TIME_BAS_MINUTE + TZ_BAS), ('23Z', time(23, tzinfo=UTC), TIME_HOUR + TZ_BAS), - ('23:20:30Z', time(23, 20, 30, tzinfo=UTC), TIME_EXT_COMPLETE + TZ_EXT), + ('23:20:30Z', time(23, 20, 30, tzinfo=UTC), + TIME_EXT_COMPLETE + TZ_EXT), ('23:20Z', time(23, 20, tzinfo=UTC), TIME_EXT_MINUTE + TZ_EXT), - ('152746+0100', time(15, 27, 46, tzinfo=FixedOffset(1, 0, '+0100')), TIME_BAS_COMPLETE + TZ_BAS), - ('152746-0500', time(15, 27, 46, tzinfo=FixedOffset(-5, 0, '-0500')), TIME_BAS_COMPLETE + TZ_BAS), - ('152746+01', time(15, 27, 46, + ('152746+0100', time(15, 27, 46, + tzinfo=FixedOffset(1, 0, '+0100')), + TIME_BAS_COMPLETE + TZ_BAS), + ('152746-0500', time(15, 27, 46, + tzinfo=FixedOffset(-5, 0, '-0500')), + TIME_BAS_COMPLETE + TZ_BAS), + ('152746+01', time(15, 27, 46, tzinfo=FixedOffset(1, 0, '+01:00')), - TIME_BAS_COMPLETE + TZ_HOUR), - ('152746-05', time(15, 27, 46, + TIME_BAS_COMPLETE + TZ_HOUR), + ('152746-05', time(15, 27, 46, tzinfo=FixedOffset(-5, -0, '-05:00')), - TIME_BAS_COMPLETE + TZ_HOUR), - ('15:27:46+01:00', time(15, 27, 46, + TIME_BAS_COMPLETE + TZ_HOUR), + ('15:27:46+01:00', time(15, 27, 46, tzinfo=FixedOffset(1, 0, '+01:00')), - TIME_EXT_COMPLETE + TZ_EXT), - ('15:27:46-05:00', time(15, 27, 46, + TIME_EXT_COMPLETE + TZ_EXT), + ('15:27:46-05:00', time(15, 27, 46, tzinfo=FixedOffset(-5, -0, '-05:00')), - TIME_EXT_COMPLETE + TZ_EXT), - ('15:27:46+01', time(15, 27, 46, + TIME_EXT_COMPLETE + TZ_EXT), + ('15:27:46+01', time(15, 27, 46, tzinfo=FixedOffset(1, 0, '+01:00')), - TIME_EXT_COMPLETE + TZ_HOUR), - ('15:27:46-05', time(15, 27, 46, + TIME_EXT_COMPLETE + TZ_HOUR), + ('15:27:46-05', time(15, 27, 46, tzinfo=FixedOffset(-5, -0, '-05:00')), - TIME_EXT_COMPLETE + TZ_HOUR), + TIME_EXT_COMPLETE + TZ_HOUR), ('1:17:30', None, TIME_EXT_COMPLETE)] - + def create_testcase(timestring, expectation, format): - ''' + """ Create a TestCase class for a specific test. - + This allows having a separate TestCase for each test tuple from the TEST_CASES list, so that a failed test won't stop other tests. - ''' - + """ + class TestTime(unittest.TestCase): ''' A test case template to parse an ISO time string into a time object. ''' - + def test_parse(self): ''' Parse an ISO time string and compare it to the expected value. @@ -100,7 +110,7 @@ def create_testcase(timestring, expectation, format): else: result = parse_time(timestring) self.assertEqual(result, expectation) - + def test_format(self): ''' Take time object and create ISO string from it. @@ -112,9 +122,10 @@ def create_testcase(timestring, expectation, format): elif format is not None: self.assertEqual(time_isoformat(expectation, format), timestring) - + return unittest.TestLoader().loadTestsFromTestCase(TestTime) + def test_suite(): ''' Construct a TestSuite instance for all test cases. |