swiftdateswiftuidate-comparison

Check if between two dates is no longer working under iOS16.3


Using iOS16.3, XCode14.2, Swift5.7.2,

Why is the following method no longer working ?

I call this method by setting date = Date() and maximumDate = Date() as well...

According to this solution, it should work - but it doesn't

public class THManager : ObservableObject {
    
    @Published public var minimumDate: Date = Date()
    @Published public var maximumDate: Date = Date()

    public func isBetweenMinAndMaxDates(date: Date) -> Bool {
        print(min(minimumDate, maximumDate))
        print(max(minimumDate, maximumDate))
        print(min(minimumDate, maximumDate)...max(minimumDate, maximumDate))
        print(date)
        print((min(minimumDate, maximumDate)...max(minimumDate, maximumDate)).contains(date))
        
        return (min(minimumDate, maximumDate)...max(minimumDate, maximumDate)).contains(date)
    }
}
2022-02-08 19:45:51 +0000
2023-02-03 19:45:51 +0000
2022-02-08 19:45:51 +0000...2023-02-03 19:45:51 +0000
2023-02-03 19:45:51 +0000
false

It supposed to return true ! Why does it return false ???

By the way it works if date = Date() and maximumDate = Date().addingTimeInterval(1)

Very strange, isn't it ?


Solution

  • There is no need for such complexity. Date objects conform to the Comparable and Equatable protocols, so testing for a date being between 2 other dates is one line:

    extension Date {
        func between(start: Date, end: Date) -> Bool {
           return self > start && self < end
        }
    }
    

    You'd use that like this:

    let date = Date()
    
    if date.betweeen(start: someDate, end: someOtherDate) {
        // the date is between the start and the end
    } else {
        // The date is not between the start and end dates
    }
    

    The above will only return true if the date in question is not equal to start or end date. You could easily change it to match dates that match the beginning and end dates by using >= and <= instead of > and < in the comparisons.

    And as discussed in the comments, Date objects have sub-millisecond precision. Two dates that appear identical may be different by a tiny fraction of a second, the best way to verify your comparisons is to convert your Dates to decimal seconds and log the seconds values. (See the Date property timeIntervalSinceReferenceDate.)

    Edit:

    Check out this sample code using the above exension:

    extension Date {
        func between(start: Date, end: Date) -> Bool {
           return self > start && self < end
        }
        var asStringWithDecimal: String {
            return DateFormatter.localizedString(from: self, dateStyle: .medium, timeStyle: .medium) + " ( \(self.timeIntervalSinceReferenceDate) seconds)"
        }
    }
    let now = Date()
    
    for _ in (1...5) {
        let random = Double.random(in: -1.5...1.5)
        let test = now.advanced(by: random)
        let start = now.advanced(by: -1)
        let end = now.advanced(by: 1)
    
        let isBetween = test.between(start: start, end: end)
        let isOrNot = isBetween ? "is" : "is not"
    
        let output = "\(test.asStringWithDecimal) \n  \(isOrNot) between \n    \(start.asStringWithDecimal) and \n    \(end.asStringWithDecimal)"
        print(output)
    }
    

    That will generate 5 random dates ± 1.5 seconds from the current date, and then test each one to see if it is within 1 second of the current date. It logs the result as both Date strings and Doubles, so you can see what's happening when the seconds match (but the fractions of a second likely don't match.)