From 6ac6c959002f0b986f52b83e03d616afd3f1f54e Mon Sep 17 00:00:00 2001 From: Alessio <148966056+alessio-locatelli@users.noreply.github.com> Date: Wed, 7 Oct 2026 08:08:37 +0300 Subject: [PATCH] fix: convert aware expiration datetimes to UTC convert_to_utc_naive() discarded the result of astimezone() and only stripped tzinfo, so an aware datetime with a non-UTC offset kept its local wall-clock time. Since internal datetimes are naive UTC, an `expire_after` of 12:00+02:00 expired at 12:00 UTC instead of 10:00 UTC. --- HISTORY.md | 1 + aiohttp_client_cache/cache_control.py | 3 +-- test/unit/test_cache_control.py | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 6b019d2..628c4a6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,6 +3,7 @@ ## Not released - Dropped Python 3.9 and 3.10 support due to the EOL. +- Fixed `expire_after` datetimes with a non-UTC timezone, which lost their UTC offset instead of being converted to UTC. - Fixed cache key collisions between distinct JSON request bodies. Cache keys now use the configured JSON serializer, including bytes serializers. - Fixed cache key collisions between request bodies, such as `data=b'false'` and `json=False`, and between request bodies and headers. Existing cache entries won't be reused after upgrading. diff --git a/aiohttp_client_cache/cache_control.py b/aiohttp_client_cache/cache_control.py index 0abb9f7..8851afa 100644 --- a/aiohttp_client_cache/cache_control.py +++ b/aiohttp_client_cache/cache_control.py @@ -243,8 +243,7 @@ def convert_to_utc_naive(dt: datetime): datetimes to the same format. """ if dt.tzinfo: - dt.astimezone(UTC) - dt = dt.replace(tzinfo=None) + dt = dt.astimezone(UTC).replace(tzinfo=None) return dt diff --git a/test/unit/test_cache_control.py b/test/unit/test_cache_control.py index 9290bf4..c3012d1 100644 --- a/test/unit/test_cache_control.py +++ b/test/unit/test_cache_control.py @@ -3,7 +3,7 @@ import os from collections.abc import Mapping from contextlib import nullcontext -from datetime import UTC, datetime, timedelta +from datetime import UTC, datetime, timedelta, timezone from email.utils import format_datetime from typing import Any from unittest.mock import MagicMock, patch @@ -333,6 +333,10 @@ def test_split_kv_directive(value, expected_output) -> None: RANDOM_DATETIME_NOW_UTC.replace(tzinfo=None), RANDOM_DATETIME_NOW_UTC.replace(tzinfo=None), ), + ( + datetime(2026, 1, 1, 12, tzinfo=timezone(timedelta(hours=2))), + datetime(2026, 1, 1, 10), + ), ], ) def test_convert_to_utc_naive(dt: datetime, dt_utc: datetime) -> None: