diff --git a/src/google/adk/sessions/database_session_service.py b/src/google/adk/sessions/database_session_service.py index 529bbfd4ff..35b30162b8 100644 --- a/src/google/adk/sessions/database_session_service.py +++ b/src/google/adk/sessions/database_session_service.py @@ -297,7 +297,8 @@ async def create_session( # Store the session now = datetime.now(timezone.utc) is_sqlite = self.db_engine.dialect.name == "sqlite" - if is_sqlite: + is_postgres = self.db_engine.dialect.name == "postgresql" + if is_sqlite or is_postgres: now = now.replace(tzinfo=None) storage_session = schema.StorageSession( diff --git a/tests/unittests/sessions/test_session_service.py b/tests/unittests/sessions/test_session_service.py index f644593402..ac77e77d38 100644 --- a/tests/unittests/sessions/test_session_service.py +++ b/tests/unittests/sessions/test_session_service.py @@ -85,6 +85,47 @@ def fake_create_async_engine(_db_url: str, **kwargs): assert captured_kwargs.get('pool_pre_ping') is True +@pytest.mark.parametrize('dialect_name', ['sqlite', 'postgresql']) +def test_database_session_service_strips_timezone_for_dialect(dialect_name): + """Verifies that timezone-aware datetimes are converted to naive datetimes + for SQLite and PostgreSQL to avoid 'can't subtract offset-naive and + offset-aware datetimes' errors. + + PostgreSQL's default TIMESTAMP type is WITHOUT TIME ZONE, which cannot + accept timezone-aware datetime objects when using asyncpg. SQLite also + requires naive datetimes. + """ + # Simulate the logic in create_session + is_sqlite = dialect_name == 'sqlite' + is_postgres = dialect_name == 'postgresql' + + now = datetime.now(timezone.utc) + assert now.tzinfo is not None # Starts with timezone + + if is_sqlite or is_postgres: + now = now.replace(tzinfo=None) + + # Both SQLite and PostgreSQL should have timezone stripped + assert now.tzinfo is None + + +def test_database_session_service_preserves_timezone_for_other_dialects(): + """Verifies that timezone info is preserved for dialects that support it.""" + # For dialects like MySQL with explicit timezone support, we don't strip + dialect_name = 'mysql' + is_sqlite = dialect_name == 'sqlite' + is_postgres = dialect_name == 'postgresql' + + now = datetime.now(timezone.utc) + assert now.tzinfo is not None + + if is_sqlite or is_postgres: + now = now.replace(tzinfo=None) + + # MySQL should preserve timezone (if the column type supports it) + assert now.tzinfo is not None + + def test_database_session_service_respects_pool_pre_ping_override(): captured_kwargs = {}