8000 Avoid IEEE754 converting fraction of seconds · PyMySQL/PyMySQL@b94c23e · GitHub
[go: up one dir, main page]

Skip to content

Commit b94c23e

Browse files
committed
Avoid IEEE754 converting fraction of seconds
Due to the limitations of IEEE754, we may lost precision when converting string back to datetime objects, for example: `float('0.511581`) * 1e6`, gives us `0.511580`, which is unexpected. In order to address that issue, here `Decimal` objects are used instead of `float`.
1 parent 2525d0a commit b94c23e

File tree

2 files changed

+18
-4
lines changed

2 files changed

+18
-4
lines changed

pymysql/converters.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ def escape_date(obj, mapping=None):
145145
def escape_struct_time(obj, mapping=None):
146146
return escape_datetime(datetime.datetime(*obj[:6]))
147147

148+
def _convert_second_fraction(fraction_str):
149+
return int(Decimal('0.' + fraction_str) * Decimal('1000000.0'))
150+
148151
def convert_datetime(obj):
149152
"""Returns a DATETIME or TIMESTAMP column value as a datetime object:
150153
@@ -175,7 +178,7 @@ def convert_datetime(obj):
175178
usecs = '0'
176179
if '.' in hms:
177180
hms, usecs = hms.split('.')
178-
usecs = float('0.' + usecs) * 1e6
181+
usecs = _convert_second_fraction(usecs)
179182
return datetime.datetime(*[ int(x) for x in ymd.split('-')+hms.split(':')+[usecs] ])
180183
except ValueError:
181184
return convert_date(obj)
@@ -204,7 +207,7 @@ def convert_timedelta(obj):
204207
microseconds = 0
205208
if "." in obj:
206209
(obj, tail) = obj.split('.')
207-
microseconds = float('0.' + tail) * 1e6
210+
microseconds = _convert_second_fraction(tail)
208211
hours, minutes, seconds = obj.split(':')
209212
negate = 1
210213
if hours.startswith("-"):
@@ -248,7 +251,7 @@ def convert_time(obj):
248251
microseconds = 0
249252
if "." in obj:
250253
(obj, tail) = obj.split('.')
251-
microseconds = float('0.' + tail) * 1e6
254+
microseconds = _convert_second_fraction(tail)
252255
hours, minutes, seconds = obj.split(':')
253256
return datetime.time(hour=int(hours), minute=int(minutes),
254257
second=int(seconds), microsecond=int(microseconds))
@@ -324,7 +327,7 @@ def through(x):
324327
#def convert_bit(b):
325328
# b = "\x00" * (8 - len(b)) + b # pad w/ zeroes
326329
# return struct.unpack(">Q", b)[0]
327-
#
330+
#
328331
# the snippet above is right, but MySQLdb doesn't process bits,
329332
# so we shouldn't either
330333
convert_bit = through

pymysql/tests/test_converters.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import datetime
12
from unittest import TestCase
23

34
from pymysql._compat import PY2
@@ -21,3 +22,13 @@ def test_escape_string_bytes(self):
2122
converters.escape_string(b"foo\nbar"),
2223
b"foo\\nbar"
2324
)
25+
26+
def test_convert_datetime_with_fsp(self):
27+
expected = datetime.datetime(2007, 2, 24, 23, 6, 20, 511581)
28+
dt = converters.convert_datetime('2007-02-24T23:06:20.511581')
29+
self.assertEqual(dt, expected)
30+
31+
def test_convert_time_with_fsp(self):
32+
expected = datetime.time(23, 6, 20, 511581)
33+
time_obj = converters.convert_time('23:06:20.511581')
34+
self.assertEqual(time_obj, expected)

0 commit comments

Comments
 (0)
0