66import sys
77import threading
88from datetime import datetime , timedelta
9+ import logging
910
1011import pytz
1112
1213from usage_analyzer .api import analyze_usage
1314from usage_analyzer .themes import get_themed_console , print_themed , ThemeType
15+ from usage_analyzer .utils .timezone_utils import TimezoneHandler , safe_timezone_conversion
1416
1517# All internal calculations use UTC, display timezone is configurable
1618UTC_TZ = pytz .UTC
@@ -154,13 +156,14 @@ def calculate_hourly_burn_rate(blocks, current_time):
154156 if not start_time_str :
155157 continue
156158
157- # Parse start time - data from usage_analyzer is in UTC
158- start_time = datetime .fromisoformat (start_time_str .replace ("Z" , "+00:00" ))
159- # Ensure it's in UTC for calculations
160- if start_time .tzinfo is None :
161- start_time = UTC_TZ .localize (start_time )
162- else :
163- start_time = start_time .astimezone (UTC_TZ )
159+ # Parse start time using robust timestamp parsing
160+ tz_handler = TimezoneHandler ()
161+ try :
162+ start_time = tz_handler .parse_timestamp (start_time_str )
163+ start_time = tz_handler .ensure_utc (start_time )
164+ except Exception as e :
165+ logging .debug (f"Failed to parse start time '{ start_time_str } ': { e } " )
166+ continue
164167
165168 # Skip gaps
166169 if block .get ("isGap" , False ):
@@ -174,14 +177,12 @@ def calculate_hourly_burn_rate(blocks, current_time):
174177 # For completed sessions, use actualEndTime or current time
175178 actual_end_str = block .get ("actualEndTime" )
176179 if actual_end_str :
177- session_actual_end = datetime .fromisoformat (
178- actual_end_str .replace ("Z" , "+00:00" )
179- )
180- # Ensure it's in UTC for calculations
181- if session_actual_end .tzinfo is None :
182- session_actual_end = UTC_TZ .localize (session_actual_end )
183- else :
184- session_actual_end = session_actual_end .astimezone (UTC_TZ )
180+ try :
181+ session_actual_end = tz_handler .parse_timestamp (actual_end_str )
182+ session_actual_end = tz_handler .ensure_utc (session_actual_end )
183+ except Exception as e :
184+ logging .debug (f"Failed to parse actual end time '{ actual_end_str } ': { e } " )
185+ session_actual_end = current_time
185186 else :
186187 session_actual_end = current_time
187188
@@ -530,12 +531,15 @@ def main():
530531 screen_buffer .append ("" )
531532
532533 # Predictions - convert to configured timezone for display
533- try :
534- local_tz = pytz .timezone (args .timezone )
535- except pytz .exceptions .UnknownTimeZoneError :
536- local_tz = pytz .timezone ("Europe/Warsaw" )
537- predicted_end_local = predicted_end_time .astimezone (local_tz )
538- reset_time_local = reset_time .astimezone (local_tz )
534+ tz_handler = TimezoneHandler (default_tz = "Europe/Warsaw" )
535+ if not tz_handler .validate_timezone (args .timezone ):
536+ print_themed (f"Invalid timezone '{ args .timezone } ', using Europe/Warsaw" , style = "warning" )
537+ timezone_to_use = "Europe/Warsaw"
538+ else :
539+ timezone_to_use = args .timezone
540+
541+ predicted_end_local = tz_handler .convert_to_timezone (predicted_end_time , timezone_to_use )
542+ reset_time_local = tz_handler .convert_to_timezone (reset_time , timezone_to_use )
539543
540544 predicted_end_str = predicted_end_local .strftime ("%H:%M" )
541545 reset_time_str = reset_time_local .strftime ("%H:%M" )
0 commit comments