From a5e00314ef80ed0f1c4dacbd8c3ea0a02cfd1d00 Mon Sep 17 00:00:00 2001 From: Hardik Kaurani Date: Fri, 25 Sep 2026 10:59:33 +0530 Subject: [PATCH 1/3] fix(alerts): handle dictionary payload for crashlytics digestDate --- src/firebase_functions/private/util.py | 9 +++++++-- tests/test_util.py | 8 ++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/firebase_functions/private/util.py b/src/firebase_functions/private/util.py index d91d13e..409a70e 100644 --- a/src/firebase_functions/private/util.py +++ b/src/firebase_functions/private/util.py @@ -369,8 +369,13 @@ def normalize_timestamp_string(time: str) -> str: return f"{prefix}.{digits[:6]}{timezone}" -def timestamp_conversion(time: str) -> _dt.datetime: - """Converts an ISO 8601 timestamp and returns a timezone-aware datetime object.""" +def timestamp_conversion(time: str | dict) -> _dt.datetime: + """Converts an ISO 8601 timestamp or dictionary and returns a timezone-aware datetime object.""" + if isinstance(time, dict): + seconds = int(time.get("seconds", 0)) + nanos = int(time.get("nanos", time.get("nanoseconds", 0))) + return _dt.datetime.fromtimestamp(seconds + (nanos / 1e9), tz=_dt.timezone.utc) + normalized_time = normalize_timestamp_string(time) if "." not in normalized_time: return _dt.datetime.strptime(normalized_time, "%Y-%m-%dT%H:%M:%S%z") diff --git a/tests/test_util.py b/tests/test_util.py index 71ce614..d895a44 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -57,6 +57,14 @@ def test_timestamp_conversion_supported_formats(): Testing shared timestamp conversion handles supported RTDB and CloudEvent formats. """ timestamps = [ + ( + {"seconds": 1712750400, "nanos": 0}, + _dt.datetime(2024, 4, 10, 12, 0, tzinfo=_dt.timezone.utc), + ), + ( + {"seconds": 1712750400, "nanos": 123456000}, + _dt.datetime(2024, 4, 10, 12, 0, 0, 123456, tzinfo=_dt.timezone.utc), + ), ( "2024-04-10T12:00:00.000Z", _dt.datetime(2024, 4, 10, 12, 0, tzinfo=_dt.timezone.utc), From a858599e46916990fd6ea6b02a5600dce64f63ed Mon Sep 17 00:00:00 2001 From: Hardik Kaurani Date: Fri, 25 Sep 2026 11:02:20 +0530 Subject: [PATCH 2/3] address code review: use timedelta to prevent precision loss and safely handle None values --- src/firebase_functions/private/util.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/firebase_functions/private/util.py b/src/firebase_functions/private/util.py index 409a70e..d668df4 100644 --- a/src/firebase_functions/private/util.py +++ b/src/firebase_functions/private/util.py @@ -372,9 +372,16 @@ def normalize_timestamp_string(time: str) -> str: def timestamp_conversion(time: str | dict) -> _dt.datetime: """Converts an ISO 8601 timestamp or dictionary and returns a timezone-aware datetime object.""" if isinstance(time, dict): - seconds = int(time.get("seconds", 0)) - nanos = int(time.get("nanos", time.get("nanoseconds", 0))) - return _dt.datetime.fromtimestamp(seconds + (nanos / 1e9), tz=_dt.timezone.utc) + seconds = time.get("seconds") + seconds = int(seconds) if seconds is not None else 0 + + nanos = time.get("nanos") + if nanos is None: + nanos = time.get("nanoseconds") + nanos = int(nanos) if nanos is not None else 0 + + base_dt = _dt.datetime.fromtimestamp(seconds, tz=_dt.timezone.utc) + return base_dt + _dt.timedelta(microseconds=nanos // 1000) normalized_time = normalize_timestamp_string(time) if "." not in normalized_time: From 87082453d87701e253c41a3e1e180858b56e26ba Mon Sep 17 00:00:00 2001 From: Hardik Kaurani Date: Fri, 25 Sep 2026 12:15:13 +0530 Subject: [PATCH 3/3] Trigger CLA check