summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGerhard Weis <gweis@gmx.at>2010-10-13 09:32:20 +1000
committerGerhard Weis <gweis@gmx.at>2010-10-13 09:32:20 +1000
commitbf641c3e00cece5673b7e96ded2d4d4d72b804aa (patch)
tree0b966befb1856cd12e476d9e0bdf403aa925d861 /src
parentde8b4fcb5b87c03ee175fa7649b6bbb4cdee20a5 (diff)
downloadisodate-bf641c3e00cece5673b7e96ded2d4d4d72b804aa.tar.gz
* fixed precision problem with microseconds
Diffstat (limited to 'src')
-rw-r--r--src/isodate/isotime.py55
-rw-r--r--src/tests/test_time.py63
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.