Fix game times with UTC data, restructure schedule by date
- Update games_canonical.json to use ISO 8601 UTC timestamps (game_datetime_utc) - Fix BootstrapService timezone-aware parsing for venue-local fallback - Fix thread-unsafe shared DateFormatter in RichGame local time display - Bump SchemaVersion to 4 to force re-bootstrap with correct UTC data - Restructure schedule view: group by date instead of sport, with sport icons on each row and date section headers showing game counts - Fix schedule row backgrounds using Theme.cardBackground instead of black - Sort games by UTC time with local-time tiebreaker for same-instant games Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -413,23 +413,20 @@ final class BootstrapService {
|
||||
let teams = (try? context.fetch(FetchDescriptor<CanonicalTeam>())) ?? []
|
||||
let stadiumByTeamId = Dictionary(uniqueKeysWithValues: teams.map { ($0.canonicalId, $0.stadiumCanonicalId) })
|
||||
|
||||
// Build stadium timezone lookup for correct local time parsing
|
||||
let stadiums = (try? context.fetch(FetchDescriptor<CanonicalStadium>())) ?? []
|
||||
let timezoneByStadiumId: [String: TimeZone] = stadiums.reduce(into: [:]) { dict, stadium in
|
||||
if let tzId = stadium.timezoneIdentifier, let tz = TimeZone(identifier: tzId) {
|
||||
dict[stadium.canonicalId] = tz
|
||||
}
|
||||
}
|
||||
|
||||
for jsonGame in games {
|
||||
// Deduplicate
|
||||
guard !seenGameIds.contains(jsonGame.canonical_id) else { continue }
|
||||
seenGameIds.insert(jsonGame.canonical_id)
|
||||
|
||||
// Parse datetime: prefer ISO 8601 format, fall back to date+time
|
||||
let dateTime: Date?
|
||||
if let iso8601String = jsonGame.game_datetime_utc {
|
||||
dateTime = parseISO8601(iso8601String)
|
||||
} else if let date = jsonGame.date {
|
||||
dateTime = parseDateTime(date: date, time: jsonGame.time ?? "7:00p")
|
||||
} else {
|
||||
dateTime = nil
|
||||
}
|
||||
|
||||
guard let dateTime else { continue }
|
||||
|
||||
// Resolve stadium ID first (needed for timezone lookup)
|
||||
let explicitStadium = jsonGame.stadium_canonical_id?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let resolvedStadiumCanonicalId: String
|
||||
@@ -445,6 +442,20 @@ final class BootstrapService {
|
||||
resolvedStadiumCanonicalId = "stadium_placeholder_\(jsonGame.canonical_id)"
|
||||
}
|
||||
|
||||
// Parse datetime: prefer ISO 8601 format, fall back to date+time
|
||||
// Times in JSON are venue-local, so parse in the stadium's timezone
|
||||
let venueTimeZone = timezoneByStadiumId[resolvedStadiumCanonicalId]
|
||||
let dateTime: Date?
|
||||
if let iso8601String = jsonGame.game_datetime_utc {
|
||||
dateTime = parseISO8601(iso8601String)
|
||||
} else if let date = jsonGame.date {
|
||||
dateTime = parseDateTime(date: date, time: jsonGame.time ?? "7:00p", timeZone: venueTimeZone)
|
||||
} else {
|
||||
dateTime = nil
|
||||
}
|
||||
|
||||
guard let dateTime else { continue }
|
||||
|
||||
let game = CanonicalGame(
|
||||
canonicalId: jsonGame.canonical_id,
|
||||
schemaVersion: SchemaVersion.current,
|
||||
@@ -534,10 +545,14 @@ final class BootstrapService {
|
||||
return formatter.date(from: string)
|
||||
}
|
||||
|
||||
nonisolated private func parseDateTime(date: String, time: String) -> Date? {
|
||||
nonisolated private func parseDateTime(date: String, time: String, timeZone: TimeZone? = nil) -> Date? {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
|
||||
// Use the venue's timezone so "1:05p" is interpreted as 1:05 PM at the stadium
|
||||
let tz = timeZone ?? .current
|
||||
formatter.timeZone = tz
|
||||
|
||||
// Parse date
|
||||
formatter.dateFormat = "yyyy-MM-dd"
|
||||
guard let dateOnly = formatter.date(from: date) else { return nil }
|
||||
@@ -563,7 +578,9 @@ final class BootstrapService {
|
||||
minute = m
|
||||
}
|
||||
|
||||
return Calendar.current.date(bySettingHour: hour, minute: minute, second: 0, of: dateOnly)
|
||||
var calendar = Calendar.current
|
||||
calendar.timeZone = tz
|
||||
return calendar.date(bySettingHour: hour, minute: minute, second: 0, of: dateOnly)
|
||||
}
|
||||
|
||||
nonisolated private func stateFromCity(_ city: String) -> String {
|
||||
|
||||
Reference in New Issue
Block a user