from datetime import datetime, time, timedelta def hms_to_seconds(hms_str): """Converts GTFS 'HH:MM:SS' (e.g. '25:30:00') to total seconds from midnight.""" h, m, s = map(int, hms_str.split(':')) return (h * 3600) + (m * 60) + s def get_service_day_start_ts(): """ Returns the Unix timestamp (UTC) for 00:00:00 of the CURRENT service day. TTC service day flips at 4:00 AM Eastern time (handles DST automatically). All timestamps are in UTC to match GTFS Realtime timestamps. """ from datetime import timezone try: # Python 3.9+ has zoneinfo built-in from zoneinfo import ZoneInfo eastern_tz = ZoneInfo("America/Toronto") except ImportError: # Fallback for older Python versions - use pytz if available try: import pytz eastern_tz = pytz.timezone("America/Toronto") except ImportError: # Last resort: hardcoded UTC-5 (EST), no DST handling now_utc = datetime.now(timezone.utc) if now_utc.hour < 9: # 4 AM Eastern = 9 AM UTC (EST) service_date = now_utc.date() - timedelta(days=1) else: service_date = now_utc.date() service_start_utc = datetime.combine(service_date, time.min, tzinfo=timezone.utc) + timedelta(hours=5) return int(service_start_utc.timestamp()) # Get current UTC time and convert to Eastern now_utc = datetime.now(timezone.utc) now_eastern = now_utc.astimezone(eastern_tz) # TTC service day flips at 4:00 AM Eastern if now_eastern.hour < 4: service_date = now_eastern.date() - timedelta(days=1) else: service_date = now_eastern.date() # Create datetime at 00:00:00 Eastern, convert to UTC try: # zoneinfo (Python 3.9+) service_start_eastern = datetime.combine(service_date, time.min, tzinfo=eastern_tz) except TypeError: # pytz needs localize service_start_eastern = eastern_tz.localize(datetime.combine(service_date, time.min)) service_start_utc = service_start_eastern.astimezone(timezone.utc) return int(service_start_utc.timestamp()) def translate_occupancy(status): """Maps GTFS occupancy enums to human readable strings.""" mapping = { 0: "Empty", 1: "Many Seats Available", 2: "Few Seats Available", 3: "No Seats Available", 5: "Full", 6: "Not In Service" } return mapping.get(status, "Full") # when in doubt assume the bus is full