Home > OS >  Swift - Given a fixed array of Strings (times), find the next closest time
Swift - Given a fixed array of Strings (times), find the next closest time

Time:10-09

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)
}
  • Related