Skip to content

Commit 98df5a6

Browse files
fix: normalize_datetime_to_server_timezone returns naive datetime
Complete the timezone handling fix by ensuring normalized datetimes are naive (no tzinfo), matching the format of stored event timestamps. This allows proper datetime comparison in event_service without TypeError. Co-authored-by: openhands <openhands@all-hands.dev>
1 parent 2e3cafe commit 98df5a6

File tree

2 files changed

+17
-10
lines changed

2 files changed

+17
-10
lines changed

openhands-agent-server/openhands/agent_server/event_router.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,25 @@
3838

3939
def normalize_datetime_to_server_timezone(dt: datetime) -> datetime:
4040
"""
41-
Normalize datetime to server timezone for consistent comparison.
41+
Normalize datetime to server timezone for consistent comparison with events.
4242
43-
If the datetime has timezone info, convert to server native timezone.
43+
Event timestamps are stored as naive datetimes in server local time.
44+
This function ensures filter datetimes are also naive in server local time
45+
so they can be compared correctly.
46+
47+
If the datetime has timezone info, convert to server native timezone and
48+
strip the tzinfo to make it naive.
4449
If it's naive (no timezone), assume it's already in server timezone.
4550
4651
Args:
4752
dt: Input datetime (may be timezone-aware or naive)
4853
4954
Returns:
50-
Datetime in server native timezone (timezone-aware)
55+
Naive datetime in server local time
5156
"""
5257
if dt.tzinfo is not None:
53-
# Timezone-aware: convert to server native timezone
54-
return dt.astimezone(None)
58+
# Timezone-aware: convert to server native timezone, then make naive
59+
return dt.astimezone(None).replace(tzinfo=None)
5560
else:
5661
# Naive datetime: assume it's already in server timezone
5762
return dt

tests/agent_server/test_event_router_websocket.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ async def test_after_timestamp_passed_to_search_events(
509509
async def test_after_timestamp_timezone_aware_is_normalized(
510510
self, mock_websocket, mock_event_service, sample_conversation_id
511511
):
512-
"""Test that timezone-aware timestamps are normalized to server timezone."""
512+
"""Test that timezone-aware timestamps are normalized to naive server time."""
513513
from datetime import datetime
514514
from typing import cast
515515

@@ -554,14 +554,16 @@ async def test_after_timestamp_timezone_aware_is_normalized(
554554
)
555555

556556
# search_events should be called with the normalized timestamp
557-
# (converted to server local timezone, which is still tz-aware)
557+
# (converted to server local timezone AND made naive for comparison)
558558
mock_event_service.search_events.assert_called_once()
559559
call_args = mock_event_service.search_events.call_args
560560
passed_timestamp = call_args.kwargs["timestamp__gte"]
561-
# The timestamp should have been converted to local timezone
561+
# The timestamp should be naive (no tzinfo)
562562
assert passed_timestamp is not None
563-
# It should represent the same instant in time
564-
assert passed_timestamp == test_timestamp.astimezone(None)
563+
assert passed_timestamp.tzinfo is None
564+
# It should represent the same instant in time (converted to local)
565+
expected = test_timestamp.astimezone(None).replace(tzinfo=None)
566+
assert passed_timestamp == expected
565567

566568
@pytest.mark.asyncio
567569
async def test_after_timestamp_without_resend_all_no_effect(

0 commit comments

Comments
 (0)