Skip to content

Commit d76795f

Browse files
committed
Fix sub-second precision loss in datetime_to_timestamp
The previous implementation used `delta.seconds + delta.days * 24 * 3600` which discards microseconds from the timedelta, effectively flooring datetime arguments to second resolution. This causes `container.logs()` to return extra logs before `since` and miss valid logs before `until` when sub-second precision matters. Replace with `delta.total_seconds()` which correctly returns a float preserving microsecond precision. The Docker API already accepts fractional timestamps, and the callers already handle float values. Fixes #3342
1 parent df3f8e2 commit d76795f

File tree

2 files changed

+30
-1
lines changed

2 files changed

+30
-1
lines changed

docker/utils/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ def convert_filters(filters):
405405
def datetime_to_timestamp(dt):
406406
"""Convert a datetime to a Unix timestamp"""
407407
delta = dt.astimezone(timezone.utc) - datetime(1970, 1, 1, tzinfo=timezone.utc)
408-
return delta.seconds + delta.days * 24 * 3600
408+
return delta.total_seconds()
409409

410410

411411
def parse_bytes(s):

tests/unit/utils_test.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import base64
2+
from datetime import datetime, timezone
23
import json
34
import os
45
import os.path
@@ -15,6 +16,7 @@
1516
compare_version,
1617
convert_filters,
1718
convert_volume_binds,
19+
datetime_to_timestamp,
1820
decode_json_header,
1921
format_environment,
2022
kwargs_from_env,
@@ -658,3 +660,30 @@ def test_compare_versions():
658660
assert compare_version('1', '1.0') == 0
659661
assert compare_version('1.10', '1.10.1') == 1
660662
assert compare_version('1.10.0', '1.10') == 0
663+
664+
665+
class DatetimeToTimestampTest(unittest.TestCase):
666+
def test_preserves_microsecond_precision(self):
667+
dt = datetime(2025, 6, 1, 12, 0, 0, 500_000, tzinfo=timezone.utc)
668+
ts = datetime_to_timestamp(dt)
669+
assert isinstance(ts, float)
670+
assert ts == 1748779200.5
671+
672+
def test_whole_second_datetime(self):
673+
dt = datetime(2025, 6, 1, 12, 0, 0, tzinfo=timezone.utc)
674+
ts = datetime_to_timestamp(dt)
675+
assert ts == 1748779200.0
676+
677+
def test_epoch(self):
678+
dt = datetime(1970, 1, 1, tzinfo=timezone.utc)
679+
ts = datetime_to_timestamp(dt)
680+
assert ts == 0.0
681+
682+
def test_naive_datetime(self):
683+
# Naive datetimes are treated as local time via astimezone()
684+
dt = datetime(2025, 6, 1, 12, 0, 0, 500_000)
685+
ts = datetime_to_timestamp(dt)
686+
# Just verify it's a float with sub-second precision preserved
687+
assert isinstance(ts, float)
688+
frac = ts % 1
689+
assert abs(frac - 0.5) < 1e-6

0 commit comments

Comments
 (0)