0

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 ?

iKK
  • 6,394
  • 10
  • 58
  • 131
  • 1
    It looks like the date you're testing is exactly the same as the upper bound in this string representation. Is it possible that it's actually some fraction of a second after the upper bound? `TimeInterval` (and thus `Date`) is accurate to less than a millisecond, so this string format loses some precision – Michael Hulet Feb 03 '23 at 20:05
  • good thought, thank you. This must exactly be the case. It seems that timing plays an important role here.... – iKK Feb 03 '23 at 20:13

1 Answers1

1

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.)

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • thanks a lot Duncan - very nice solution. What tricked me indeed, is the fact that my maximumDate=Date() happened a fraction of a second earlier than date=Date() - which makes the two different ! And the log does not take that into account. – iKK Feb 03 '23 at 20:29
  • See the edit to my answer. I added some test code that logs the dates as date strings **and** doubles so you can see what's going on with dates that are very close to the start or end date. – Duncan C Feb 03 '23 at 20:41
  • If you want to know if your `Date` is between your two other dates to the second, you can truncate the value of your dates' timeIntervalSinceReferenceDate values and compare those. If you want to compare to some other precision like minutes, hours, or days, you could use the Calendar function `compare(_:to:toGranularity:)`. That will let you tell if your dates are in the same second, minute, hour, day, week, or whatever you want. – Duncan C Feb 03 '23 at 20:47