Skip to content

Commit

Permalink
Fix: TimeDelta Precision Errors (#1867)
Browse files Browse the repository at this point in the history
* Use microsecond integers to fix timedelta precision errors

* Update changelog

Co-authored-by: Steven Loria <[email protected]>
  • Loading branch information
deckar01 and sloria authored Oct 17, 2021
1 parent bfd2593 commit d2a0cdb
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ Changelog
3.14.0 (unreleased)
*******************

Bug fixes:

- Fix ``fields.TimeDelta`` serialization precision (:issue:`1865`).
Thanks :user:`yarsanich` for reporting.

Other changes:

- Fix type-hints for ```data``` arg in ```Schema.validate``` to accept
Expand Down
4 changes: 3 additions & 1 deletion src/marshmallow/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -1472,7 +1472,9 @@ def _serialize(self, value, attr, obj, **kwargs):
if value is None:
return None
base_unit = dt.timedelta(**{self.precision: 1})
return int(value.total_seconds() / base_unit.total_seconds())
delta = utils.timedelta_to_microseconds(value)
unit = utils.timedelta_to_microseconds(base_unit)
return delta // unit

def _deserialize(self, value, attr, data, **kwargs):
try:
Expand Down
8 changes: 8 additions & 0 deletions src/marshmallow/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,11 @@ def resolve_field_instance(cls_or_instance):
if not isinstance(cls_or_instance, FieldABC):
raise FieldInstanceResolutionError
return cls_or_instance


def timedelta_to_microseconds(value: dt.timedelta) -> int:
"""Compute the total microseconds of a timedelta
https://github.com/python/cpython/blob/bb3e0c240bc60fe08d332ff5955d54197f79751c/Lib/datetime.py#L665-L667 # noqa: B950
"""
return (value.days * (24 * 3600) + value.seconds) * 1000000 + value.microseconds
9 changes: 9 additions & 0 deletions tests/test_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,15 @@ def test_timedelta_field(self, user):
user.d7 = None
assert field.serialize("d7", user) is None

# https://github.com/marshmallow-code/marshmallow/issues/1856
user.d8 = dt.timedelta(milliseconds=345)
field = fields.TimeDelta(fields.TimeDelta.MILLISECONDS)
assert field.serialize("d8", user) == 345

user.d9 = dt.timedelta(milliseconds=1999)
field = fields.TimeDelta(fields.TimeDelta.SECONDS)
assert field.serialize("d9", user) == 1

def test_datetime_list_field(self):
obj = DateTimeList([dt.datetime.utcnow(), dt.datetime.now()])
field = fields.List(fields.DateTime)
Expand Down

0 comments on commit d2a0cdb

Please sign in to comment.