From ecb0ea871aa8459878c9c10734a7aaf1a248261d Mon Sep 17 00:00:00 2001 From: Reginald Alfret Date: Sat, 14 Nov 2026 10:38:58 +0530 Subject: [PATCH] fix: preserve days and formatting in TimeDeltaSerializer._to_dict_value Calculate total seconds from both days and seconds so durations >= 1 day are not truncated modulo 86400s. Fix nanos division and zero-padding so fractional second strings conform to protobuf JSON duration format and round-trip cleanly through _from_dict_value. Closes #73 --- kaggle_object_tests.py | 30 ++++++++++++++++++++++++++++++ kagglesdk/kaggle_object.py | 16 +++++++++------- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/kaggle_object_tests.py b/kaggle_object_tests.py index 62a455e..d5663ed 100644 --- a/kaggle_object_tests.py +++ b/kaggle_object_tests.py @@ -617,6 +617,36 @@ def test_quota_response_deserializes_whole_second_durations(self): datetime.timedelta(seconds=151), ) + def test_days_duration_serialization(self): + # timedelta of 30 hours (days=1, seconds=21600) should serialize to 108000s, not 21600s + d = datetime.timedelta(seconds=108000) + self.assertEqual(TimeDeltaSerializer._to_dict_value(d), "108000s") + + def test_fractional_second_serialization(self): + d = datetime.timedelta(seconds=11247, microseconds=735000) + self.assertEqual(TimeDeltaSerializer._to_dict_value(d), "11247.735s") + + def test_subsecond_padding_serialization(self): + d_ms = datetime.timedelta(microseconds=5000) + self.assertEqual(TimeDeltaSerializer._to_dict_value(d_ms), "0.005s") + d_us = datetime.timedelta(microseconds=7) + self.assertEqual(TimeDeltaSerializer._to_dict_value(d_us), "0.000007s") + + def test_round_trip_serialization(self): + cases = [ + datetime.timedelta(0), + datetime.timedelta(seconds=151), + datetime.timedelta(seconds=108000), + datetime.timedelta(seconds=11247, microseconds=735000), + datetime.timedelta(microseconds=5000), + datetime.timedelta(microseconds=7), + datetime.timedelta(days=3, seconds=14, microseconds=123456), + ] + for d in cases: + serialized = TimeDeltaSerializer._to_dict_value(d) + deserialized = TimeDeltaSerializer._from_dict_value(serialized) + self.assertEqual(d, deserialized) + if __name__ == "__main__": unittest.main() diff --git a/kagglesdk/kaggle_object.py b/kagglesdk/kaggle_object.py index b2b0fc5..1888bb5 100644 --- a/kagglesdk/kaggle_object.py +++ b/kagglesdk/kaggle_object.py @@ -165,11 +165,13 @@ def __init__(self): @staticmethod def _to_dict_value(delta): - seconds = delta.seconds - nanos_str = TimeDeltaSerializer._nanos_to_str(delta.microseconds * 1000) + total_us = (delta.days * 86400 + delta.seconds) * 1_000_000 + delta.microseconds + sign = "-" if total_us < 0 else "" + seconds, micros = divmod(abs(total_us), 1_000_000) + nanos_str = TimeDeltaSerializer._nanos_to_str(micros * 1000) if nanos_str is None: - return "{}s".format(seconds) - return "{}.{}s".format(seconds, nanos_str) + return "{}{}s".format(sign, seconds) + return "{}{}.{}s".format(sign, seconds, nanos_str) @staticmethod def _nanos_to_str(nanos): @@ -178,11 +180,11 @@ def _nanos_to_str(nanos): if nanos < 0: nanos *= -1 if nanos % 1000000 == 0: - return "{}".format(nanos / 1000000) + return "{:03d}".format(nanos // 1000000) elif nanos % 1000 == 0: - return "{}".format(nanos / 1000) + return "{:06d}".format(nanos // 1000) else: - return "{}".format(nanos) + return "{:09d}".format(nanos) @staticmethod def _from_dict_value(value):