fix(display): use stadium timezone for all game time displays

Game times were incorrectly using device timezone in several views.
Now all game time displays use the stadium's local timezone via
RichGame.localGameTime/localGameTimeShort properties.

Fixes:
- PDFGenerator: was using game.gameDate (midnight UTC) instead of actual time
- GameRowCompact: was formatting with device timezone
- TimelineGameRow: was using .formatted() with device timezone

Also adds Date+GameTime.swift extension for centralized timezone formatting.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-20 22:44:40 -06:00
parent 74fd21590b
commit 166ad5d6f9
4 changed files with 75 additions and 9 deletions

View File

@@ -0,0 +1,70 @@
//
// Date+GameTime.swift
// SportsTime
//
// Centralized game time formatting with timezone support.
//
import Foundation
extension Date {
/// Formats the date as a game time string in the specified timezone.
///
/// - Parameters:
/// - timeZone: The timezone to display the time in (typically the stadium's timezone).
/// Falls back to the device's current timezone if nil.
/// - includeZone: Whether to include the timezone abbreviation (e.g., "EDT", "PST").
/// - Returns: A formatted time string like "7:00 PM" or "7:00 PM EDT".
///
/// Usage:
/// ```swift
/// // Without timezone indicator (for UI cards)
/// game.dateTime.gameTimeString(in: stadium.timeZone) // "7:00 PM"
///
/// // With timezone indicator (for exports, detail views)
/// game.dateTime.gameTimeString(in: stadium.timeZone, includeZone: true) // "7:00 PM EDT"
/// ```
func gameTimeString(in timeZone: TimeZone?, includeZone: Bool = false) -> String {
let formatter = DateFormatter()
formatter.dateFormat = includeZone ? "h:mm a z" : "h:mm a"
formatter.timeZone = timeZone ?? .current
return formatter.string(from: self)
}
/// Formats the date as a full game date and time string in the specified timezone.
///
/// - Parameters:
/// - timeZone: The timezone to display in (typically the stadium's timezone).
/// - includeZone: Whether to include the timezone abbreviation.
/// - Returns: A formatted string like "Sat, Jan 18 at 7:00 PM" or "Sat, Jan 18 at 7:00 PM EDT".
func gameDateTimeString(in timeZone: TimeZone?, includeZone: Bool = false) -> String {
let formatter = DateFormatter()
formatter.dateFormat = includeZone ? "EEE, MMM d 'at' h:mm a z" : "EEE, MMM d 'at' h:mm a"
formatter.timeZone = timeZone ?? .current
return formatter.string(from: self)
}
/// Returns the start of day in the specified timezone.
///
/// This is useful for grouping games by calendar day in the stadium's local timezone,
/// not the user's device timezone.
///
/// - Parameter timeZone: The timezone to calculate start of day in.
/// - Returns: A Date representing midnight in the specified timezone.
func startOfDay(in timeZone: TimeZone?) -> Date {
var calendar = Calendar.current
calendar.timeZone = timeZone ?? .current
return calendar.startOfDay(for: self)
}
/// Returns the calendar day components in the specified timezone.
///
/// - Parameter timeZone: The timezone to extract components in.
/// - Returns: DateComponents with year, month, and day.
func calendarDay(in timeZone: TimeZone?) -> DateComponents {
var calendar = Calendar.current
calendar.timeZone = timeZone ?? .current
return calendar.dateComponents([.year, .month, .day], from: self)
}
}