diff --git a/firebase_admin/_messaging_encoder.py b/firebase_admin/_messaging_encoder.py index b7c69107..9aa38536 100644 --- a/firebase_admin/_messaging_encoder.py +++ b/firebase_admin/_messaging_encoder.py @@ -16,7 +16,6 @@ import datetime import json -import math import numbers import re import warnings @@ -275,14 +274,9 @@ def encode_ttl(cls, ttl): if not isinstance(ttl, datetime.timedelta): raise ValueError('AndroidConfig.ttl must be a duration in seconds or an instance of ' 'datetime.timedelta.') - total_seconds = ttl.total_seconds() - if total_seconds < 0: + if ttl < datetime.timedelta(0): raise ValueError('AndroidConfig.ttl must not be negative.') - seconds = int(math.floor(total_seconds)) - nanos = int((total_seconds - seconds) * 1e9) - if nanos: - return f'{seconds}.{str(nanos).zfill(9)}s' - return f'{seconds}s' + return cls.encode_duration(ttl) @classmethod def encode_milliseconds(cls, label, msec): @@ -294,13 +288,19 @@ def encode_milliseconds(cls, label, msec): if not isinstance(msec, datetime.timedelta): raise ValueError( f'{label} must be a duration in milliseconds or an instance of datetime.timedelta.') - total_seconds = msec.total_seconds() - if total_seconds < 0: + if msec < datetime.timedelta(0): raise ValueError(f'{label} must not be negative.') - seconds = int(math.floor(total_seconds)) - nanos = int((total_seconds - seconds) * 1e9) + return cls.encode_duration(msec) + + @classmethod + def encode_duration(cls, duration): + """Encodes a non-negative ``datetime.timedelta`` into a protobuf Duration string.""" + # Use the exact integer fields of the timedelta instead of total_seconds(), which + # is a float and can turn e.g. 86400.9 seconds into 86400.899999999s. + seconds = duration.days * 86400 + duration.seconds + nanos = duration.microseconds * 1000 if nanos: - return f'{seconds}.{str(nanos).zfill(9)}s' + return f'{seconds}.{nanos:09d}s' return f'{seconds}s' @classmethod diff --git a/tests/test_messaging.py b/tests/test_messaging.py index ae2e4a4d..e5783227 100644 --- a/tests/test_messaging.py +++ b/tests/test_messaging.py @@ -466,6 +466,9 @@ def test_android_config(self): (123, '123s'), (123.45, '123.450000000s'), (datetime.timedelta(days=1, seconds=100), '86500s'), + (1.123457, '1.123457000s'), + (86400.9, '86400.900000000s'), + (datetime.timedelta(days=1, microseconds=900000), '86400.900000000s'), ]) def test_android_ttl(self, ttl): msg = messaging.Message(