简体   繁体   中英

Check if current time is between range of times considering after midnight - Swift

I have an opening and closing hours of restaurants from rest service:

[
   {
      "weekday":0,
      "openingAt":"07:30:00",
      "closingAt":"00:45:00"
   },
   {
      "weekday":1,
      "openingAt":"07:30:00",
      "closingAt":"23:00:00"
   },
   {
      "weekday":2,
      "openingAt":"07:30:00",
      "closingAt":"23:00:00"
   },
   {
      "weekday":3,
      "openingAt":"07:30:00",
      "closingAt":"23:00:00"
   },
   {
      "weekday":4,
      "openingAt":"07:30:00",
      "closingAt":"23:00:00"
   },
   {
      "weekday":5,
      "openingAt":"07:30:00",
      "closingAt":"23:00:00"
   },
   {
      "weekday":6,
      "openingAt":"07:30:00",
      "closingAt":"01:00:00"
   }
]

I have created computed property for a check that:

var isClosed: Bool {
    let todayIndex = (Calendar.current.component(.weekday, from: Date()) - 1) % 7
    let yesterdayIndex = (Calendar.current.component(.weekday, from: Date()) - 2) % 7

    let todayDate = Date()
    if let wh = workingHour, wh.count > 7 {
        let todayWh = wh[todayIndex]
        if let openingStr = todayWh.openingAt, openingStr != "",
            let openingDate = Date().setTimeHHmmss(formattedString: openingStr),
            let closingStr = todayWh.closingAt, closingStr != "",
            let closingDate = Date().setTimeHHmmss(formattedString: closingStr)
        {
            let yesterdayWh = wh[yesterdayIndex]
            var fromYesterdayExtraHours = 0
            if let yesterdayClosingStr = yesterdayWh.closingAt,
                let yClosingDate = Date().setTimeHHmmss(formattedString: yesterdayClosingStr) {
                if yClosingDate.hour > 0 {
                    fromYesterdayExtraHours = yClosingDate.hour
                }
            }
            if  closingDate < openingDate {
                if todayDate.hour < fromYesterdayExtraHours {
                    return false // opened
                } else {
                    return true // closed
                }
            } else if todayDate >= openingDate && todayDate <= closingDate {
                return false // opened
            } else {
                return true // closed
            }
        }
    }
    return false // opened
}

What I do is:

  1. Converting the strings to date object by setting the time on today object
  2. Then checking if the closing time is on the next day (the worst part)
  3. Then checking if the closing time is less than opening time, (like this: "openingAt":"07:30:00", "closingAt":"01:00:00"
  4. Then checking if the current time is between opening and closing time

Any Swifty way suggestion to fix this mess?

My suggestion is to decode the JSON into a struct with Decodable and the opening and closing times for convenience reasons into DateComponents .

I assume that weekday == 0 is Sunday

let jsonString = """
[
{"weekday":0, "openingAt":"07:30:00", "closingAt":"00:45:00"},
{"weekday":1, "openingAt":"07:30:00", "closingAt":"23:00:00"},
{"weekday":2, "openingAt":"07:30:00", "closingAt":"23:00:00"},
{"weekday":3, "openingAt":"07:30:00", "closingAt":"23:00:00"},
{"weekday":4, "openingAt":"07:30:00", "closingAt":"23:00:00"},
{"weekday":5, "openingAt":"07:30:00", "closingAt":"23:00:00"},
{"weekday":6, "openingAt":"07:30:00", "closingAt":"01:00:00"}
]
"""

struct Schedule : Decodable {
    let weekday : Int
    let openingAt : DateComponents
    let closingAt : DateComponents

    private enum CodingKeys: String, CodingKey { case weekday, openingAt, closingAt }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        weekday = (try container.decode(Int.self, forKey: .weekday)) + 1
        let open = try container.decode(String.self, forKey: .openingAt)
        let openComponents = open.components(separatedBy:":")
        openingAt = DateComponents(hour: Int(openComponents[0]), minute: Int(openComponents[1]), second: Int(openComponents[2]))
        let close = try container.decode(String.self, forKey: .closingAt)
        let closeComponents = close.components(separatedBy:":")
        closingAt = DateComponents(hour: Int(closeComponents[0]), minute: Int(closeComponents[1]), second: Int(closeComponents[2]))
    }
}

First declare now, start of today and the current calendar

let now = Date()
let calendar = Calendar.current
let midnight = calendar.startOfDay(for: now)

Then decode the JSON, filter the schedule for today by its weekday, create opening and closing time by adding the date components and compare the dates. endDate is calculated by finding the next occurrence of given date components after the start date. This solves the issue if the closing date is tomorrow.

do {
    let data = Data(jsonString.utf8)
    let schedule = try JSONDecoder().decode([Schedule].self, from: data)
    let todaySchedule = schedule.first{ $0.weekday == calendar.component(.weekday, from: now) }

    let startDate = calendar.date(byAdding: todaySchedule!.openingAt, to: midnight)!
    let endDate = calendar.nextDate(after: startDate, matching: todaySchedule!.closingAt, matchingPolicy: .nextTime)!
    let isOpen = now >= startDate && now < endDate


} catch { print(error) }

There is one limitation: The code does not work if the current time is in the range 0:00 to closing time (for example Sunday and Monday). That's a challenge for you 😉

Here is my solution that takes a little different approach by using a struct with minute and hour as Int's

struct Time: Comparable {
    var hour = 0
    var minute = 0

    init(hour: Int, minute: Int) {
        self.hour = hour
        self.minute = minute
    }
    init(_ date: Date) {
        let calendar = Calendar.current
        hour = calendar.component(.hour, from: date)
        minute = calendar.component(.minute, from: date)
    }

    static func == (lhs: Time, rhs: Time) -> Bool {
        return lhs.hour == rhs.hour && lhs.minute == rhs.minute
    }

    static func < (lhs: Time, rhs: Time) -> Bool {
        return (lhs.hour < rhs.hour) || (lhs.hour == rhs.hour && lhs.minute < rhs.minute)
    }

    static func create(time: String) -> Time? {
        let parts = time.split(separator: ":")
        if let hour = Int(parts[0]), let minute = Int(parts[1]) {
            return Time(hour: hour, minute: minute)
        }
        return nil
    }

    static func isOpen(open: Time, close: Time) -> Bool {
        let isClosingAfterMidnight = close.hour < open.hour ? true : false
        let currentTime = Time(Date())

        if isClosingAfterMidnight {
            return currentTime > close && currentTime < open ? false : true
        }
        return currentTime >= open && currentTime < close
    }
}

And it can be used like

if let open = Time.create(time: todayWh.openingAt), let close = Time.create(time: todayWh.closingAt) {
    return Time.isOpen(open: open, close: close))
} else { 
  //error handling
}

It should work also after midnight :) Of course the Time struct could be used directly in the wh array.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM