Files
letta-server/tests/test_timezone_formatting.py
2025-06-24 12:57:37 -07:00

222 lines
10 KiB
Python

import json
from datetime import datetime
import pytest
import pytz
from letta.helpers.datetime_helpers import get_local_time, get_local_time_timezone
from letta.system import (
get_heartbeat,
get_login_event,
package_function_response,
package_summarize_message,
package_system_message,
package_user_message,
)
class TestTimezoneFormatting:
"""Test suite for timezone formatting functions in system.py"""
def _extract_time_from_json(self, json_str: str) -> str:
"""Helper to extract time field from JSON string"""
data = json.loads(json_str)
return data["time"]
def _validate_timezone_accuracy(self, formatted_time: str, expected_timezone: str, tolerance_minutes: int = 2):
"""
Validate that the formatted time is accurate for the given timezone within tolerance.
Args:
formatted_time: The time string from the system functions
expected_timezone: The timezone string (e.g., "America/New_York")
tolerance_minutes: Acceptable difference in minutes
"""
# Parse the formatted time - handle the actual format produced
# Expected format: "2025-06-24 12:53:40 AM EDT-0400"
import re
from datetime import timedelta, timezone
# Match pattern like "2025-06-24 12:53:40 AM EDT-0400"
pattern = r"(\d{4}-\d{2}-\d{2} \d{1,2}:\d{2}:\d{2} [AP]M) ([A-Z]{3,4})([-+]\d{4})"
match = re.match(pattern, formatted_time)
if not match:
# Fallback: just check basic format without detailed parsing
assert len(formatted_time) > 20, f"Time string too short: {formatted_time}"
assert " AM " in formatted_time or " PM " in formatted_time, f"No AM/PM in time: {formatted_time}"
return
time_part, tz_name, tz_offset = match.groups()
# Parse the time part without timezone
time_without_tz = datetime.strptime(time_part, "%Y-%m-%d %I:%M:%S %p")
# Create timezone offset
hours_offset = int(tz_offset[:3])
minutes_offset = int(tz_offset[3:5]) if len(tz_offset) > 3 else 0
if tz_offset[0] == "-" and hours_offset >= 0:
hours_offset = -hours_offset
total_offset = timedelta(hours=hours_offset, minutes=minutes_offset)
tz_info = timezone(total_offset)
parsed_time = time_without_tz.replace(tzinfo=tz_info)
# Get current time in the expected timezone
tz = pytz.timezone(expected_timezone)
current_time_in_tz = datetime.now(tz)
# Check that times are within tolerance
time_diff = abs((parsed_time - current_time_in_tz).total_seconds())
assert time_diff <= tolerance_minutes * 60, (
f"Time difference too large: {time_diff}s. " f"Parsed: {parsed_time}, Expected timezone: {current_time_in_tz}"
)
# Verify timezone info exists and format looks reasonable
assert parsed_time.tzinfo is not None, "Parsed time should have timezone info"
assert tz_name in formatted_time, f"Timezone abbreviation {tz_name} should be in formatted time"
def test_get_heartbeat_timezone_accuracy(self):
"""Test that get_heartbeat produces accurate timestamps for different timezones"""
test_timezones = ["UTC", "America/New_York", "America/Los_Angeles", "Europe/London", "Asia/Tokyo"]
for tz in test_timezones:
heartbeat = get_heartbeat(timezone=tz, reason="Test heartbeat")
time_str = self._extract_time_from_json(heartbeat)
self._validate_timezone_accuracy(time_str, tz)
def test_get_login_event_timezone_accuracy(self):
"""Test that get_login_event produces accurate timestamps for different timezones"""
test_timezones = ["UTC", "US/Eastern", "US/Pacific", "Australia/Sydney"]
for tz in test_timezones:
login = get_login_event(timezone=tz, last_login="2024-01-01")
time_str = self._extract_time_from_json(login)
self._validate_timezone_accuracy(time_str, tz)
def test_package_user_message_timezone_accuracy(self):
"""Test that package_user_message produces accurate timestamps for different timezones"""
test_timezones = ["UTC", "America/Chicago", "Europe/Paris", "Asia/Shanghai"]
for tz in test_timezones:
message = package_user_message("Test message", timezone=tz)
time_str = self._extract_time_from_json(message)
self._validate_timezone_accuracy(time_str, tz)
def test_package_function_response_timezone_accuracy(self):
"""Test that package_function_response produces accurate timestamps for different timezones"""
test_timezones = ["UTC", "America/Denver", "Europe/Berlin", "Pacific/Auckland"]
for tz in test_timezones:
response = package_function_response(True, "Success", timezone=tz)
time_str = self._extract_time_from_json(response)
self._validate_timezone_accuracy(time_str, tz)
def test_package_system_message_timezone_accuracy(self):
"""Test that package_system_message produces accurate timestamps for different timezones"""
test_timezones = ["UTC", "America/Phoenix", "Europe/Rome", "Asia/Kolkata"] # Mumbai is now called Kolkata in pytz
for tz in test_timezones:
message = package_system_message("System alert", timezone=tz)
time_str = self._extract_time_from_json(message)
self._validate_timezone_accuracy(time_str, tz)
def test_package_summarize_message_timezone_accuracy(self):
"""Test that package_summarize_message produces accurate timestamps for different timezones"""
test_timezones = ["UTC", "America/Anchorage", "Europe/Stockholm", "Asia/Seoul"]
for tz in test_timezones:
summary = package_summarize_message(
summary="Test summary", summary_message_count=2, hidden_message_count=5, total_message_count=7, timezone=tz
)
time_str = self._extract_time_from_json(summary)
self._validate_timezone_accuracy(time_str, tz)
def test_get_local_time_timezone_direct(self):
"""Test get_local_time_timezone directly for accuracy"""
test_timezones = ["UTC", "America/New_York", "Europe/London", "Asia/Tokyo", "Australia/Melbourne"]
for tz in test_timezones:
time_str = get_local_time_timezone(timezone=tz)
self._validate_timezone_accuracy(time_str, tz)
def test_get_local_time_with_timezone_param(self):
"""Test get_local_time when timezone parameter is provided"""
test_timezones = ["UTC", "America/Los_Angeles", "Europe/Madrid", "Asia/Bangkok"]
for tz in test_timezones:
time_str = get_local_time(timezone=tz)
self._validate_timezone_accuracy(time_str, tz)
def test_timezone_offset_differences(self):
"""Test that different timezones produce appropriately offset times"""
# Get times for different timezones at the same moment
utc_heartbeat = get_heartbeat(timezone="UTC")
utc_time_str = self._extract_time_from_json(utc_heartbeat)
ny_heartbeat = get_heartbeat(timezone="America/New_York")
ny_time_str = self._extract_time_from_json(ny_heartbeat)
tokyo_heartbeat = get_heartbeat(timezone="Asia/Tokyo")
tokyo_time_str = self._extract_time_from_json(tokyo_heartbeat)
# Just validate that all times have the expected format
# UTC should have UTC in the string
assert "UTC" in utc_time_str, f"UTC timezone not found in: {utc_time_str}"
# NY should have EST or EDT
assert any(tz in ny_time_str for tz in ["EST", "EDT"]), f"EST/EDT not found in: {ny_time_str}"
# Tokyo should have JST
assert "JST" in tokyo_time_str, f"JST not found in: {tokyo_time_str}"
def test_daylight_saving_time_handling(self):
"""Test that DST transitions are handled correctly"""
# Test timezone that observes DST
eastern_tz = "America/New_York"
# Get current time in Eastern timezone
message = package_user_message("DST test", timezone=eastern_tz)
time_str = self._extract_time_from_json(message)
# Validate against current Eastern time
self._validate_timezone_accuracy(time_str, eastern_tz)
# The timezone abbreviation should be either EST or EDT
assert any(tz in time_str for tz in ["EST", "EDT"]), f"EST/EDT not found in: {time_str}"
@pytest.mark.parametrize(
"timezone_str,expected_format_parts",
[
("UTC", ["UTC", "+0000"]),
("America/New_York", ["EST", "EDT"]), # Either EST or EDT depending on date
("Europe/London", ["GMT", "BST"]), # Either GMT or BST depending on date
("Asia/Tokyo", ["JST", "+0900"]),
("Australia/Sydney", ["AEDT", "AEST"]), # Either AEDT or AEST depending on date
],
)
def test_timezone_format_components(self, timezone_str, expected_format_parts):
"""Test that timezone formatting includes expected components"""
heartbeat = get_heartbeat(timezone=timezone_str)
time_str = self._extract_time_from_json(heartbeat)
# Check that at least one expected format part is present
found_expected_part = any(part in time_str for part in expected_format_parts)
assert found_expected_part, f"None of expected format parts {expected_format_parts} found in time string: {time_str}"
# Validate the time is accurate
self._validate_timezone_accuracy(time_str, timezone_str)
def test_timezone_parameter_working(self):
"""Test that timezone parameter correctly affects the output"""
# Test that different timezones produce different time formats
utc_message = package_user_message("Test", timezone="UTC")
utc_time = self._extract_time_from_json(utc_message)
ny_message = package_user_message("Test", timezone="America/New_York")
ny_time = self._extract_time_from_json(ny_message)
# Times should have different timezone indicators
assert "UTC" in utc_time, f"UTC not found in: {utc_time}"
assert any(tz in ny_time for tz in ["EST", "EDT"]), f"EST/EDT not found in: {ny_time}"