From bf420b748073176f06024b629561ba4925028b0d Mon Sep 17 00:00:00 2001 From: Tony Bark <35226681+tonytins@users.noreply.github.com> Date: Mon, 24 Aug 2026 05:51:47 -0400 Subject: [PATCH] Time column accounts for destination's time zone --- .../AmtrakStatus/AmtrakDateFormatting.swift | 17 +++- Sources/AmtrakStatus/ContentView.swift | 80 ++++++++++++++----- Sources/AmtrakStatus/Models.swift | 17 ++-- 3 files changed, 81 insertions(+), 33 deletions(-) diff --git a/Sources/AmtrakStatus/AmtrakDateFormatting.swift b/Sources/AmtrakStatus/AmtrakDateFormatting.swift index d1f4fdd..98042ec 100644 --- a/Sources/AmtrakStatus/AmtrakDateFormatting.swift +++ b/Sources/AmtrakStatus/AmtrakDateFormatting.swift @@ -26,13 +26,26 @@ struct AmtrakDateFormatting { return isoFormatter.date(from: isoString) } - func time(from isoString: String?, timeZone: TimeZone? = nil) -> String? { + func time(from isoString: String?, timeZone: TimeZone?, relativeTo otherZone: TimeZone? = nil) -> String? { guard let isoString, !isoString.isEmpty, let date = isoFormatter.date( from: isoString) else { return nil } + timeFormatter.timeZone = timeZone - return timeFormatter.string(from: date) + + let formatted = timeFormatter.string(from: date) + + guard + let timeZone, + let otherZone, + timeZone.identifier != otherZone.identifier, + let abbreviation = timeZone.abbreviation(for: date) + else { + return formatted + } + + return "\(formatted) \(abbreviation)" } func localTime(timeZone: TimeZone?) -> String { diff --git a/Sources/AmtrakStatus/ContentView.swift b/Sources/AmtrakStatus/ContentView.swift index 0b25506..ea9df2b 100644 --- a/Sources/AmtrakStatus/ContentView.swift +++ b/Sources/AmtrakStatus/ContentView.swift @@ -51,12 +51,12 @@ struct ContentView: View { } Table(rows) { - TableColumn("Time", value: \TrainStatusRow.arrival) + TableColumn("Time", value: \TrainStatusRow.time) TableColumn("Number", value: \TrainStatusRow.trainNum) TableColumn("", value: \TrainStatusRow.presence.rawValue) - TableColumn("Train", value: \TrainStatusRow.routeName) - TableColumn("To", value: \TrainStatusRow.destination) - TableColumn("From", value: \TrainStatusRow.origin) + TableColumn("Train", value: \TrainStatusRow.train) + TableColumn("To", value: \TrainStatusRow.to) + TableColumn("From", value: \TrainStatusRow.from) TableColumn("Status") { (row: TrainStatusRow) in ZStack { @@ -68,7 +68,7 @@ struct ContentView: View { ) } } - TableColumn("Track", value: \TrainStatusRow.platform) + TableColumn("Track", value: \TrainStatusRow.track) }.frame(minWidth: 600) .overlay(alignment: .bottomTrailing) { if isLoading { @@ -135,6 +135,8 @@ struct ContentView: View { rows = [] return } + + var zoneCache: [String: TimeZone?] = [code: lookup.timeZone] var newRows: [TrainStatusRow] = [] for number in lookup.trainNumbers { @@ -144,12 +146,13 @@ struct ContentView: View { where: { $0.code == code }, )?.platform - newRows + await newRows .append( trainStatus( for: train, atStationCode: code, - timeZone: lookup.timeZone + timeZone: lookup.timeZone, + zoneCache: &zoneCache ) ) } @@ -187,30 +190,65 @@ struct ContentView: View { } } - func trainStatus(for train: Train, atStationCode code: String, timeZone: TimeZone?) -> TrainStatusRow { + func trainStatus( + for train: Train, + atStationCode code: String, + timeZone: TimeZone?, + zoneCache: inout [String: TimeZone?] + ) async -> TrainStatusRow { let leg = train.stations?.first { $0.code == code } let stationName = leg?.name ?? unknown - let arrival = amtrakDate.time(from: leg?.arr, timeZone: timeZone) ?? amtrakDate.time( + let track = leg?.platform ?? "" + + var originZone: TimeZone? + if let orgCode = train.origCode { + originZone = await fetchTimeZone( + forStationCode: orgCode, + cache: &zoneCache + ) + } + + let time = amtrakDate.time( + from: leg?.arr, + timeZone: timeZone, + relativeTo: originZone + ) ?? amtrakDate.time( from: leg?.schArr, - timeZone: timeZone) - let depature = amtrakDate.time(from: leg?.dep, timeZone: timeZone) ?? amtrakDate.time( - from: leg?.schDep, - timeZone: timeZone) + timeZone: timeZone, + relativeTo: originZone + ) currentStation = stationName localTimeZone = timeZone return TrainStatusRow( trainID: train.trainID, + time: time ?? "", trainNum: train.trainNum, - routeName: train.routeName, - status: isOnTime(arr: leg?.arr, schArr: leg?.schArr), - platform: leg?.platform ?? "", - arrival: arrival ?? "", - departure: depature ?? "", - origin: train.origName, - destination: train.destName, - presence: leg?.status ?? .unknown // Yeah... It's a little confusing + presence: leg?.status ?? .unknown, // Yeah... It's a little confusing + train: train.routeName, + to: train.destName, + from: train.origName, + status: isOnTime( + arr: leg?.arr, + schArr: leg?.schArr + ), + track: track.isEmpty ? "" : track ) } + + func fetchTimeZone(forStationCode code: String, cache: inout [String: TimeZone?]) async -> TimeZone? { + if let cached = cache[code] { + return cached + } + let zone: TimeZone? + do { + let response = try await client.fetchStation(code: code) + zone = response[code]?.tz.flatMap(TimeZone.init(identifier:)) + } catch { + zone = nil + } + + return zone + } } diff --git a/Sources/AmtrakStatus/Models.swift b/Sources/AmtrakStatus/Models.swift index 74c9747..ece20f9 100644 --- a/Sources/AmtrakStatus/Models.swift +++ b/Sources/AmtrakStatus/Models.swift @@ -90,18 +90,15 @@ typealias TrainResponse = [String: [Train]] typealias StationResponse = [String: StationMeta] struct TrainStatusRow: Identifiable { - var id: String { - trainID - } + var id: String { trainID } var trainID: String + var time: String var trainNum: String - var routeName: String - var status: TrainLateness - var platform: String - var arrival: String - var departure: String - var origin: String - var destination: String var presence: StationStatus + var train: String + var to: String + var from: String + var status: TrainLateness + var track: String }