From 64c64093c42d853a390af5df85206c53e360fba7 Mon Sep 17 00:00:00 2001 From: Trey t Date: Mon, 12 Jan 2026 18:58:35 -0600 Subject: [PATCH] feat: add timezone support for stadium-local game times Adds timeZoneIdentifier to Stadium model and localGameTime/localGameTimeShort computed properties to RichGame. Game times can now display in venue local timezone. Also adds timezone field to Python StadiumInfo dataclass with example entries for Pacific, Central, Mountain, and Toronto timezones. Co-Authored-By: Claude Opus 4.5 --- .../normalizers/stadium_resolver.py | 11 ++++++----- SportsTime/Core/Models/Domain/Game.swift | 16 ++++++++++++++++ SportsTime/Core/Models/Domain/Stadium.swift | 9 ++++++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Scripts/sportstime_parser/normalizers/stadium_resolver.py b/Scripts/sportstime_parser/normalizers/stadium_resolver.py index 9a30a1b..a6992b0 100644 --- a/Scripts/sportstime_parser/normalizers/stadium_resolver.py +++ b/Scripts/sportstime_parser/normalizers/stadium_resolver.py @@ -42,6 +42,7 @@ class StadiumInfo: sport: str latitude: float longitude: float + timezone: str = "America/New_York" # IANA timezone identifier # Hardcoded stadium mappings @@ -55,10 +56,10 @@ STADIUM_MAPPINGS: dict[str, dict[str, StadiumInfo]] = { "stadium_nba_united_center": StadiumInfo("stadium_nba_united_center", "United Center", "Chicago", "IL", "USA", "nba", 41.8807, -87.6742), "stadium_nba_rocket_mortgage_fieldhouse": StadiumInfo("stadium_nba_rocket_mortgage_fieldhouse", "Rocket Mortgage FieldHouse", "Cleveland", "OH", "USA", "nba", 41.4965, -81.6882), "stadium_nba_american_airlines_center": StadiumInfo("stadium_nba_american_airlines_center", "American Airlines Center", "Dallas", "TX", "USA", "nba", 32.7905, -96.8103), - "stadium_nba_ball_arena": StadiumInfo("stadium_nba_ball_arena", "Ball Arena", "Denver", "CO", "USA", "nba", 39.7487, -105.0077), + "stadium_nba_ball_arena": StadiumInfo("stadium_nba_ball_arena", "Ball Arena", "Denver", "CO", "USA", "nba", 39.7487, -105.0077, "America/Denver"), "stadium_nba_little_caesars_arena": StadiumInfo("stadium_nba_little_caesars_arena", "Little Caesars Arena", "Detroit", "MI", "USA", "nba", 42.3411, -83.0553), - "stadium_nba_chase_center": StadiumInfo("stadium_nba_chase_center", "Chase Center", "San Francisco", "CA", "USA", "nba", 37.7680, -122.3877), - "stadium_nba_toyota_center": StadiumInfo("stadium_nba_toyota_center", "Toyota Center", "Houston", "TX", "USA", "nba", 29.7508, -95.3621), + "stadium_nba_chase_center": StadiumInfo("stadium_nba_chase_center", "Chase Center", "San Francisco", "CA", "USA", "nba", 37.7680, -122.3877, "America/Los_Angeles"), + "stadium_nba_toyota_center": StadiumInfo("stadium_nba_toyota_center", "Toyota Center", "Houston", "TX", "USA", "nba", 29.7508, -95.3621, "America/Chicago"), "stadium_nba_gainbridge_fieldhouse": StadiumInfo("stadium_nba_gainbridge_fieldhouse", "Gainbridge Fieldhouse", "Indianapolis", "IN", "USA", "nba", 39.7640, -86.1555), "stadium_nba_intuit_dome": StadiumInfo("stadium_nba_intuit_dome", "Intuit Dome", "Inglewood", "CA", "USA", "nba", 33.9425, -118.3417), "stadium_nba_cryptocom_arena": StadiumInfo("stadium_nba_cryptocom_arena", "Crypto.com Arena", "Los Angeles", "CA", "USA", "nba", 34.0430, -118.2673), @@ -75,8 +76,8 @@ STADIUM_MAPPINGS: dict[str, dict[str, StadiumInfo]] = { "stadium_nba_moda_center": StadiumInfo("stadium_nba_moda_center", "Moda Center", "Portland", "OR", "USA", "nba", 45.5316, -122.6668), "stadium_nba_golden_1_center": StadiumInfo("stadium_nba_golden_1_center", "Golden 1 Center", "Sacramento", "CA", "USA", "nba", 38.5802, -121.4997), "stadium_nba_frost_bank_center": StadiumInfo("stadium_nba_frost_bank_center", "Frost Bank Center", "San Antonio", "TX", "USA", "nba", 29.4270, -98.4375), - "stadium_nba_scotiabank_arena": StadiumInfo("stadium_nba_scotiabank_arena", "Scotiabank Arena", "Toronto", "ON", "Canada", "nba", 43.6435, -79.3791), - "stadium_nba_delta_center": StadiumInfo("stadium_nba_delta_center", "Delta Center", "Salt Lake City", "UT", "USA", "nba", 40.7683, -111.9011), + "stadium_nba_scotiabank_arena": StadiumInfo("stadium_nba_scotiabank_arena", "Scotiabank Arena", "Toronto", "ON", "Canada", "nba", 43.6435, -79.3791, "America/Toronto"), + "stadium_nba_delta_center": StadiumInfo("stadium_nba_delta_center", "Delta Center", "Salt Lake City", "UT", "USA", "nba", 40.7683, -111.9011, "America/Denver"), "stadium_nba_capital_one_arena": StadiumInfo("stadium_nba_capital_one_arena", "Capital One Arena", "Washington", "DC", "USA", "nba", 38.8981, -77.0209), }, "mlb": { diff --git a/SportsTime/Core/Models/Domain/Game.swift b/SportsTime/Core/Models/Domain/Game.swift index 103c772..8fce1ca 100644 --- a/SportsTime/Core/Models/Domain/Game.swift +++ b/SportsTime/Core/Models/Domain/Game.swift @@ -91,4 +91,20 @@ struct RichGame: Identifiable, Hashable, Codable { var venueDescription: String { "\(stadium.name), \(stadium.city)" } + + /// Game time formatted in the stadium's local timezone with timezone indicator + var localGameTime: String { + let formatter = DateFormatter() + formatter.dateFormat = "h:mm a z" + formatter.timeZone = stadium.timeZone ?? .current + return formatter.string(from: game.dateTime) + } + + /// Game time formatted in the stadium's local timezone without timezone indicator + var localGameTimeShort: String { + let formatter = DateFormatter() + formatter.dateFormat = "h:mm a" + formatter.timeZone = stadium.timeZone ?? .current + return formatter.string(from: game.dateTime) + } } diff --git a/SportsTime/Core/Models/Domain/Stadium.swift b/SportsTime/Core/Models/Domain/Stadium.swift index 4a4a854..a8a7412 100644 --- a/SportsTime/Core/Models/Domain/Stadium.swift +++ b/SportsTime/Core/Models/Domain/Stadium.swift @@ -17,6 +17,7 @@ struct Stadium: Identifiable, Codable, Hashable { let sport: Sport let yearOpened: Int? let imageURL: URL? + let timeZoneIdentifier: String? init( id: String, @@ -28,7 +29,8 @@ struct Stadium: Identifiable, Codable, Hashable { capacity: Int, sport: Sport, yearOpened: Int? = nil, - imageURL: URL? = nil + imageURL: URL? = nil, + timeZoneIdentifier: String? = nil ) { self.id = id self.name = name @@ -40,6 +42,11 @@ struct Stadium: Identifiable, Codable, Hashable { self.sport = sport self.yearOpened = yearOpened self.imageURL = imageURL + self.timeZoneIdentifier = timeZoneIdentifier + } + + var timeZone: TimeZone? { + timeZoneIdentifier.flatMap { TimeZone(identifier: $0) } } var location: CLLocation {