I am working on a simple iOS app with UIKit which shows you the time of the next departing bus.
I have an array of strings, which represents departure times. I have converted those strings into a DateComponents
object and tried using the .nextDate
method to access the next closest time. Unfortunately after searching and trying various options, I can't seem to figure it out. I tried a single date component object with fixed values. This worked but it's useless. I need to get the values from an array (unless there's another option I don't know about).
What my code looks so far:
let currentTime = Date()
let calendar = Calendar.current
var busTimes = ["15:02", "15:27", "15:52", "16:12", "16:37", "17:02", "17:27", "17:52", "18:12", "18:32", "18:52", "19:17"]
@IBAction func calculateRoute(_ sender: Any) {
let myDate = busTimes[0].getMyDate()
let nextBusTime = calendar.nextDate(after: currentTime, matching: myDate, matchingPolicy: .nextTime, repeatedTimePolicy: .first, direction: .forward )!
print(nextBusTime) //prints only the first value ofc
}
extension String {
func getMyDate() -> DateComponents {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
dateFormatter.timeZone = TimeZone.current
let date = dateFormatter.date(from: self)
let dateComp = Calendar.current.dateComponents([.hour, .minute], from: date!)
return dateComp
}
}
I would expect to return the next closest value. Let's say the current time is 15:15, so the output should be the next closest time which is 15:27.
Thanks in advance!
CodePudding user response:
First let's define a type that allows us to express time.
struct Time {
let hour: Int
let minute: Int
static let zero = Time(hour: 0, minute: 0)
}
Let's also implement an initializer so we can conveniently pass in stringified time values
extension Time {
init(_ string: String) {
let ints = string
.components(separatedBy: ":")
.compactMap(Int.init)
hour = ints.first ?? 0
minute = ints.last ?? 0
}
}
let timeFromString = Time("2:30")
Let's also comform to Comparable
so we can compare Time
values and sort Array
of Time
values
extension Time: Comparable {
static func < (lhs: Time, rhs: Time) -> Bool {
return lhs.hour * 60 lhs.minute < rhs.hour * 60 rhs.minute
}
}
print(Time("02:00") < Time("03:44"))
print([Time("03:44"), Time("02:00"), Time("1:30")].sorted())
true
[01:30, 02:00, 03:44]
This one is just for fun. We can initialize a Time
value with a String
literal
extension Time: ExpressibleByStringLiteral {
init(stringLiteral: String) {
self.init(stringLiteral)
}
}
let timeFromStringLiteraL: Time = "3:30"
This will allow us to convert Time
values to friendly String
values, mainly for debugging.
extension Time: CustomStringConvertible {
var description: String {
return String(format: "d:d", hour, minute)
}
}
print(Time("4:30"))
04:30
Now let's extend Calendar
so we can convert Date
values to Time
values
extension Calendar {
func time(from date: Date) -> Time? {
let timeComponents = dateComponents([.hour, .minute], from: date)
guard let hour = timeComponents.hour,
let minute = timeComponents.minute else { return nil }
return Time(hour: hour, minute: minute)
}
}
let timeFromDate = Calendar.current.time(from: Date())
Now let's create a type to manage Time
values for us. Notice how it sorts the time values when you pass them in. This sets up the next(after:)
method so it can easily find the next time after the specified time.
struct Schedule {
let times: [Time]
init(times: [Time]) {
self.times = times.sorted()
}
func next(after time: Time) -> Time? {
times.first(where: { $0 > time })
}
}
Putting it all together
let times: [Time] = ["01:22", "15:02", "15:27", "15:52", "16:12", "16:37", "20:00", "17:02", "17:27", "17:52", "18:12", "18:32", "18:52", "19:46", "18:37", "19:17", "19:32", "19:33"]
let schedule = Schedule(times: times)
let now = Calendar.current.time(from: Date())!
let next = schedule.next(after: now)
print("Next time: \(next ?? .zero)")
Or:
["01:22", "15:02", "15:27", "20:22", "20:23"]
.map { Time($0) }
.filter { $0 > now }
.sorted()
.prefix(1)
.forEach { print($0) }
CodePudding user response:
You can convert the current time to a string of the same format and then compare it to the content of the array
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
let input = dateFormatter.string(from: .now)
if let nextDepartureTime = busTimes.first(where: { input < $0 }) {
print(nextDepartureTime)
}