diff --git a/Bundler.toml b/Bundler.toml index cc33fa4..142cdbf 100644 --- a/Bundler.toml +++ b/Bundler.toml @@ -3,7 +3,7 @@ format_version = 2 [apps.AmtrakStatus] identifier = 'com.tonybark.AmtrakStatus' product = 'AmtrakStatus' -version = '0.2.102' +version = '0.2.103' icon = "./assets/icon.png" 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 09d601c..ea9df2b 100644 --- a/Sources/AmtrakStatus/ContentView.swift +++ b/Sources/AmtrakStatus/ContentView.swift @@ -51,11 +51,12 @@ struct ContentView: View { } Table(rows) { - TableColumn("Time", value: \TrainStatusRow.arrival) + TableColumn("Time", value: \TrainStatusRow.time) TableColumn("Number", value: \TrainStatusRow.trainNum) - TableColumn("Train", value: \TrainStatusRow.routeName) - TableColumn("To", value: \TrainStatusRow.destination) - TableColumn("From", value: \TrainStatusRow.origin) + TableColumn("", value: \TrainStatusRow.presence.rawValue) + TableColumn("Train", value: \TrainStatusRow.train) + TableColumn("To", value: \TrainStatusRow.to) + TableColumn("From", value: \TrainStatusRow.from) TableColumn("Status") { (row: TrainStatusRow) in ZStack { @@ -67,7 +68,7 @@ struct ContentView: View { ) } } - TableColumn("Track", value: \TrainStatusRow.platform) + TableColumn("Track", value: \TrainStatusRow.track) }.frame(minWidth: 600) .overlay(alignment: .bottomTrailing) { if isLoading { @@ -134,6 +135,8 @@ struct ContentView: View { rows = [] return } + + var zoneCache: [String: TimeZone?] = [code: lookup.timeZone] var newRows: [TrainStatusRow] = [] for number in lookup.trainNumbers { @@ -143,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 ) ) } @@ -186,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, - // serviceDate: train.serviceDate + 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 eba059c..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 presence: StationStatus + var train: String + var to: String + var from: String var status: TrainLateness - var platform: String - var arrival: String - var departure: String - var origin: String - var destination: String - // var serviceDate: String + var track: String } diff --git a/changelog.md b/changelog.md index 8a28089..91f9def 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,9 @@ # Change Log +## 0.2.103 + +- A new column after number has "Enroute," "Station," and "Departed" status. It's left blank since status is already taken. + ## 0.2.102 - The status bar at the bottom now shows ``It is