Skip to content

Commit

Permalink
WIP better support for time zones: store times always with a timezone…
Browse files Browse the repository at this point in the history
… object
  • Loading branch information
alexander-albers committed Jan 2, 2025
1 parent 47fe427 commit 9881325
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 61 deletions.
29 changes: 27 additions & 2 deletions Sources/TripKit/Model/Leg.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ public protocol Leg {
///
/// See ``NetworkProvider/timeZone`` for a discussion about how to correctly handle time zones.
var departureTime: Date { get }
var departureTimeZone: TimeZone? { get }

/// Predicted departure time, if available, otherwise the planned time.
/// See ``NetworkProvider/timeZone`` for a discussion about how to correctly handle time zones.
var arrivalTime: Date { get }
var arrivalTimeZone: TimeZone? { get }

/// Returns always the planned departure time.
///
Expand Down Expand Up @@ -67,7 +69,9 @@ public class PublicLeg: NSObject, Leg, NSSecureCoding {
public var arrival: Location { arrivalStop.location }
public let path: [LocationPoint]
public var departureTime: Date { departureStop.predictedTime ?? departureStop.plannedTime }
public var departureTimeZone: TimeZone? { departureStop.timeZone }
public var arrivalTime: Date { arrivalStop.predictedTime ?? arrivalStop.plannedTime }
public var arrivalTimeZone: TimeZone? { arrivalStop.timeZone }
public var plannedDepartureTime: Date { departureStop.plannedTime }
public var plannedArrivalTime: Date { arrivalStop.plannedTime }
public var minTime: Date { departureStop.minTime }
Expand Down Expand Up @@ -192,7 +196,9 @@ public class IndividualLeg: NSObject, Leg, NSSecureCoding {
public var arrival: Location
public let path: [LocationPoint]
public let departureTime: Date
public let departureTimeZone: TimeZone?
public let arrivalTime: Date
public let arrivalTimeZone: TimeZone?
public var plannedDepartureTime: Date { departureTime }
public var plannedArrivalTime: Date { arrivalTime }
public var minTime: Date { departureTime }
Expand All @@ -205,12 +211,14 @@ public class IndividualLeg: NSObject, Leg, NSSecureCoding {
/// Diestance in meters between departure and arrival.
public let distance: Int

public init(type: `Type`, departureTime: Date, departure: Location, arrival: Location, arrivalTime: Date, distance: Int, path: [LocationPoint]) {
public init(type: `Type`, departure: Location, arrival: Location, departureTime: Date, arrivalTime: Date, departureTimeZone: TimeZone?, arrivalTimeZone: TimeZone?, distance: Int, path: [LocationPoint]) {
self.type = type
self.departure = departure
self.arrival = arrival
self.departureTime = departureTime
self.arrivalTime = arrivalTime
self.departureTimeZone = departureTimeZone
self.arrivalTimeZone = arrivalTimeZone
self.min = Int(arrivalTime.timeIntervalSince(departureTime) / 60.0)
self.distance = distance
self.path = path
Expand All @@ -227,20 +235,35 @@ public class IndividualLeg: NSObject, Leg, NSSecureCoding {
os_log("failed to decode individual leg", log: .default, type: .error)
return nil
}

let departureTimeZone: TimeZone?
if let secondsFromGMT = aDecoder.decodeObject(of: NSNumber.self, forKey: PropertyKey.departureTimeZone) as? Int {
departureTimeZone = TimeZone(secondsFromGMT: secondsFromGMT)
} else {
departureTimeZone = nil
}
let arrivalTimeZone: TimeZone?
if let secondsFromGMT = aDecoder.decodeObject(of: NSNumber.self, forKey: PropertyKey.arrivalTimeZone) as? Int {
arrivalTimeZone = TimeZone(secondsFromGMT: secondsFromGMT)
} else {
arrivalTimeZone = nil
}
let encodedPath = aDecoder.decodeObject(of: [NSArray.self, NSNumber.self], forKey: PropertyKey.path) as? [Int] ?? []
let path = stride(from: 0, to: encodedPath.count % 2 == 0 ? encodedPath.count : 0, by: 2).map {
LocationPoint(lat: encodedPath[$0], lon: encodedPath[$0 + 1])
}
let distance = aDecoder.decodeInteger(forKey: PropertyKey.distance)
self.init(type: type, departureTime: departureTime, departure: departure, arrival: arrival, arrivalTime: arrivalTime, distance: distance, path: path)
self.init(type: type, departure: departure, arrival: arrival, departureTime: departureTime, arrivalTime: arrivalTime, departureTimeZone: departureTimeZone, arrivalTimeZone: arrivalTimeZone, distance: distance, path: path)
}

public func encode(with aCoder: NSCoder) {
aCoder.encode(type.rawValue, forKey: PropertyKey.type)
aCoder.encode(departure, forKey: PropertyKey.departure)
aCoder.encode(arrival, forKey: PropertyKey.arrival)
aCoder.encode(departureTime, forKey: PropertyKey.departureTime)
aCoder.encode(departureTimeZone?.secondsFromGMT(), forKey: PropertyKey.departureTimeZone)
aCoder.encode(arrivalTime, forKey: PropertyKey.arrivalTime)
aCoder.encode(arrivalTimeZone?.secondsFromGMT(), forKey: PropertyKey.arrivalTimeZone)
aCoder.encode(distance, forKey: PropertyKey.distance)
aCoder.encode(path.flatMap({[$0.lat, $0.lon]}), forKey: PropertyKey.path)
}
Expand All @@ -255,7 +278,9 @@ public class IndividualLeg: NSObject, Leg, NSSecureCoding {
static let departure = "departure"
static let arrival = "arrival"
static let departureTime = "departureTime"
static let departureTimeZone = "departureTimeZone"
static let arrivalTime = "arrivalTime"
static let arrivalTimeZone = "arrivalTimeZone"
static let distance = "distance"
static let path = "path"

Expand Down
24 changes: 22 additions & 2 deletions Sources/TripKit/Model/Stop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,18 @@ public class Stop: NSObject, NSSecureCoding {

let departure: StopEvent?
if let plannedDepartureTime = aDecoder.decodeObject(of: NSDate.self, forKey: PropertyKey.plannedDepartureTime) as Date? {
let secondsFromGMT = aDecoder.decodeObject(of: NSNumber.self, forKey: PropertyKey.departureTimeZone) as? Int
let timeZone: TimeZone?
if let secondsFromGMT {
timeZone = TimeZone(secondsFromGMT: secondsFromGMT)
} else {
timeZone = nil
}
departure = StopEvent(
location: location,
plannedTime: plannedDepartureTime,
predictedTime: aDecoder.decodeObject(of: NSDate.self, forKey: PropertyKey.predictedDepartureTime) as Date?,
timeZone: timeZone,
plannedPlatform: aDecoder.decodeObject(of: NSString.self, forKey: PropertyKey.plannedDeparturePlatform) as String?,
predictedPlatform: aDecoder.decodeObject(of: NSString.self, forKey: PropertyKey.predictedDeparturePlatform) as String?,
cancelled: aDecoder.decodeBool(forKey: PropertyKey.departureCancelled)
Expand All @@ -86,10 +94,18 @@ public class Stop: NSObject, NSSecureCoding {

let arrival: StopEvent?
if let plannedArrivalTime = aDecoder.decodeObject(of: NSDate.self, forKey: PropertyKey.plannedArrivalTime) as Date? {
let secondsFromGMT = aDecoder.decodeObject(of: NSNumber.self, forKey: PropertyKey.arrivalTimeZone) as? Int
let timeZone: TimeZone?
if let secondsFromGMT {
timeZone = TimeZone(secondsFromGMT: secondsFromGMT)
} else {
timeZone = nil
}
arrival = StopEvent(
location: location,
plannedTime: plannedArrivalTime,
predictedTime: aDecoder.decodeObject(of: NSDate.self, forKey: PropertyKey.predictedArrivalTime) as Date?,
timeZone: timeZone,
plannedPlatform: aDecoder.decodeObject(of: NSString.self, forKey: PropertyKey.plannedArrivalPlatform) as String?,
predictedPlatform: aDecoder.decodeObject(of: NSString.self, forKey: PropertyKey.predictedArrivalPlatform) as String?,
cancelled: aDecoder.decodeBool(forKey: PropertyKey.arrivalCancelled)
Expand All @@ -107,6 +123,7 @@ public class Stop: NSObject, NSSecureCoding {
if let departure = departure {
aCoder.encode(departure.plannedTime, forKey: PropertyKey.plannedDepartureTime)
aCoder.encode(departure.predictedTime, forKey: PropertyKey.predictedDepartureTime)
aCoder.encode(departure.timeZone?.secondsFromGMT(), forKey: PropertyKey.departureTimeZone)
aCoder.encode(departure.plannedPlatform, forKey: PropertyKey.plannedDeparturePlatform)
aCoder.encode(departure.predictedPlatform, forKey: PropertyKey.predictedDeparturePlatform)
aCoder.encode(departure.cancelled, forKey: PropertyKey.departureCancelled)
Expand All @@ -115,6 +132,7 @@ public class Stop: NSObject, NSSecureCoding {
if let arrival = arrival {
aCoder.encode(arrival.plannedTime, forKey: PropertyKey.plannedArrivalTime)
aCoder.encode(arrival.predictedTime, forKey: PropertyKey.predictedArrivalTime)
aCoder.encode(arrival.timeZone?.secondsFromGMT(), forKey: PropertyKey.arrivalTimeZone)
aCoder.encode(arrival.plannedPlatform, forKey: PropertyKey.plannedArrivalPlatform)
aCoder.encode(arrival.predictedPlatform, forKey: PropertyKey.predictedArrivalPlatform)
aCoder.encode(arrival.cancelled, forKey: PropertyKey.arrivalCancelled)
Expand All @@ -137,11 +155,13 @@ public class Stop: NSObject, NSSecureCoding {
static let location = "location"
static let plannedArrivalTime = "plannedArrivalTime"
static let predictedArrivalTime = "predictedArrivalTime"
static let arrivalTimeZone = "arrivalTimeZone"
static let plannedArrivalPlatform = "plannedArrivalPlatform"
static let predictedArrivalPlatform = "predictedArrivalPlatform"
static let arrivalCancelled = "arrivalCancelled"
static let plannedDepartureTime = "plannedDepartureTime"
static let predictedDepartureTime = "predictedDepartureTime"
static let departureTimeZone = "departureTimeZone"
static let plannedDeparturePlatform = "plannedDeparturePlatform"
static let predictedDeparturePlatform = "predictedDeparturePlatform"
static let departureCancelled = "departureCancelled"
Expand Down Expand Up @@ -184,13 +204,13 @@ extension Stop {
public convenience init(location: Location, plannedArrivalTime: Date?, predictedArrivalTime: Date?, plannedArrivalPlatform: String?, predictedArrivalPlatform: String?, arrivalCancelled: Bool, plannedDepartureTime: Date?, predictedDepartureTime: Date?, plannedDeparturePlatform: String?, predictedDeparturePlatform: String?, departureCancelled: Bool, message: String? = nil, wagonSequenceContext: URL? = nil) {
let departure: StopEvent?
if let plannedDepartureTime = plannedDepartureTime {
departure = StopEvent(location: location, plannedTime: plannedDepartureTime, predictedTime: predictedDepartureTime, plannedPlatform: plannedDeparturePlatform, predictedPlatform: predictedDeparturePlatform, cancelled: departureCancelled)
departure = StopEvent(location: location, plannedTime: plannedDepartureTime, predictedTime: predictedDepartureTime, timeZone: nil, plannedPlatform: plannedDeparturePlatform, predictedPlatform: predictedDeparturePlatform, cancelled: departureCancelled)
} else {
departure = nil
}
let arrival: StopEvent?
if let plannedArrivalTime = plannedArrivalTime {
arrival = StopEvent(location: location, plannedTime: plannedArrivalTime, predictedTime: predictedArrivalTime, plannedPlatform: plannedArrivalPlatform, predictedPlatform: predictedArrivalPlatform, cancelled: arrivalCancelled)
arrival = StopEvent(location: location, plannedTime: plannedArrivalTime, predictedTime: predictedArrivalTime, timeZone: nil, plannedPlatform: plannedArrivalPlatform, predictedPlatform: predictedArrivalPlatform, cancelled: arrivalCancelled)
} else {
arrival = nil
}
Expand Down
5 changes: 4 additions & 1 deletion Sources/TripKit/Model/StopEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public class StopEvent: NSObject {
///
/// See ``NetworkProvider/timeZone`` for a discussion about how to correctly handle time zones.
public let predictedTime: Date?
/// The timezone to be used...
public let timeZone: TimeZone?
/// Scheduled arrival/departure platform of a station.
public let plannedPlatform: String?
/// Actual arrival/departure platform of a station.
Expand Down Expand Up @@ -42,10 +44,11 @@ public class StopEvent: NSObject {
return max(plannedTime, predictedTime)
}

public init(location: Location, plannedTime: Date, predictedTime: Date?, plannedPlatform: String?, predictedPlatform: String?, cancelled: Bool) {
public init(location: Location, plannedTime: Date, predictedTime: Date?, timeZone: TimeZone?, plannedPlatform: String?, predictedPlatform: String?, cancelled: Bool) {
self.location = location
self.plannedTime = plannedTime
self.predictedTime = predictedTime
self.timeZone = timeZone
self.plannedPlatform = plannedPlatform
self.predictedPlatform = predictedPlatform
self.cancelled = cancelled
Expand Down
2 changes: 2 additions & 0 deletions Sources/TripKit/Model/Trip.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ public class Trip: NSObject, NSSecureCoding {
///
/// See ``NetworkProvider/timeZone`` for a discussion about how to correctly handle time zones.
public var departureTime: Date { legs[0].departureTime }
public var departureTimeZone: TimeZone? { legs[0].departureTimeZone }
/// Predicted arrival time of the last leg, if available, otherwise the planned time.
///
/// See ``NetworkProvider/timeZone`` for a discussion about how to correctly handle time zones.
public var arrivalTime: Date { legs[legs.count - 1].arrivalTime }
public var arrivalTimeZone: TimeZone? { legs[legs.count - 1].arrivalTimeZone }

/// Returns always the planned departure time of the first leg.
///
Expand Down
10 changes: 5 additions & 5 deletions Sources/TripKit/Provider/AbstractEfaMobileProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ public class AbstractEfaMobileProvider: AbstractEfaProvider {
guard let location = location else {
throw ParseError(reason: "failed to parse location")
}
let stop = StopEvent(location: location, plannedTime: plannedTime, predictedTime: predictedTime, plannedPlatform: position, predictedPlatform: nil, cancelled: false)
let stop = StopEvent(location: location, plannedTime: plannedTime, predictedTime: predictedTime, timeZone: nil, plannedPlatform: position, predictedPlatform: nil, cancelled: false)
switch usage {
case "departure":
departure = stop
Expand Down Expand Up @@ -383,7 +383,7 @@ public class AbstractEfaMobileProvider: AbstractEfaProvider {
if let location = location {
let stopEvent: StopEvent?
if let plannedTime = plannedTime {
stopEvent = StopEvent(location: location, plannedTime: plannedTime, predictedTime: predictedTime, plannedPlatform: nil, predictedPlatform: nil, cancelled: false)
stopEvent = StopEvent(location: location, plannedTime: plannedTime, predictedTime: predictedTime, timeZone: nil, plannedPlatform: nil, predictedPlatform: nil, cancelled: false)
} else {
stopEvent = nil
}
Expand All @@ -396,9 +396,9 @@ public class AbstractEfaMobileProvider: AbstractEfaProvider {
}
let addTime: TimeInterval = !legs.isEmpty ? max(0, -departure.minTime.timeIntervalSince(legs.last!.maxTime)) : 0
if lineDestination.line === Line.FOOTWAY {
legs.append(IndividualLeg(type: .walk, departureTime: departure.minTime.addingTimeInterval(addTime), departure: departure.location, arrival: arrival.location, arrivalTime: arrival.maxTime.addingTimeInterval(addTime), distance: 0, path: path))
legs.append(IndividualLeg(type: .walk, departure: departure.location, arrival: arrival.location, departureTime: departure.minTime.addingTimeInterval(addTime), arrivalTime: arrival.maxTime.addingTimeInterval(addTime), departureTimeZone: nil, arrivalTimeZone: nil, distance: 0, path: path))
} else if lineDestination.line === Line.TRANSFER {
legs.append(IndividualLeg(type: .transfer, departureTime: departure.minTime.addingTimeInterval(addTime), departure: departure.location, arrival: arrival.location, arrivalTime: arrival.maxTime.addingTimeInterval(addTime), distance: 0, path: path))
legs.append(IndividualLeg(type: .transfer, departure: departure.location, arrival: arrival.location, departureTime: departure.minTime.addingTimeInterval(addTime), arrivalTime: arrival.maxTime.addingTimeInterval(addTime), departureTimeZone: nil, arrivalTimeZone: nil, distance: 0, path: path))
} else if lineDestination.line === Line.DO_NOT_CHANGE {
if let last = legs.last as? PublicLeg {
var lastMessage = "Nicht umsteigen, Weiterfahrt im selben Fahrzeug möglich."
Expand Down Expand Up @@ -526,7 +526,7 @@ public class AbstractEfaMobileProvider: AbstractEfaProvider {
guard let location = Location(type: .station, id: id, coord: coord, place: place, name: name) else { throw ParseError(reason: "failed to parse stop") }
let stopEvent: StopEvent?
if let plannedTime = plannedTime {
stopEvent = StopEvent(location: location, plannedTime: plannedTime, predictedTime: nil, plannedPlatform: position, predictedPlatform: nil, cancelled: false)
stopEvent = StopEvent(location: location, plannedTime: plannedTime, predictedTime: nil, timeZone: nil, plannedPlatform: position, predictedPlatform: nil, cancelled: false)
} else {
stopEvent = nil
}
Expand Down
Loading

0 comments on commit 9881325

Please sign in to comment.