93

I am trying to figure out whether or not the current date falls within a date range using NSDate.

For example, you can get the current date/time using NSDate:

NSDate rightNow = [NSDate date];

I would then like to use that date to check if it is in the range of 9AM - 5PM.

Brock Woolf
  • 46,656
  • 50
  • 121
  • 144
  • 1
    You can take a look at the answers to [this question](http://stackoverflow.com/questions/947947/how-to-determine-if-an-nstimeinterval-occurred-during-an-arbitrary-nsdate) for more information on how create NSDate objects for a specific time period, such as 5pm-9pm. – Marc Charbonneau Jul 02 '09 at 15:14

10 Answers10

173

I came up with a solution. If you have a better solution, feel free to leave it and I will mark it as correct.

+ (BOOL)date:(NSDate*)date isBetweenDate:(NSDate*)beginDate andDate:(NSDate*)endDate
{
    if ([date compare:beginDate] == NSOrderedAscending)
        return NO;

    if ([date compare:endDate] == NSOrderedDescending) 
        return NO;

    return YES;
}
Brock Woolf
  • 46,656
  • 50
  • 121
  • 144
  • 3
    That looks good, but you might want to rename it something like this: + (BOOL)date:(NSDate*)date isBetweenDate:(NSDate*)beginDate andDate:(NSDate*)endDate in order to avoid confusion. – Jeff Kelley Jul 02 '09 at 18:40
  • Good point Jeff, your idea does make it more clear. I will update my answer. – Brock Woolf Jul 04 '09 at 05:24
  • 3
    Code golf! The compiler should short-circuit the comparison, making the efficiency roughly equal to what you have: return (([date compare:beginDate] != NSOrderedDescending) && ([date compare:endDate] != NSOrderedAscending)); – Tim Jul 04 '09 at 05:45
  • (Note: I addressed this misconception in my answer.) – Quinn Taylor Jul 13 '09 at 01:45
  • 1
    Better yet, I'd make this an instance method (an addition to NSDate), not a class method. I'd go with this: -isBetweenDate:andDate: – Dave Batton Dec 28 '10 at 18:22
  • 4
    Excellent! I made a Category from this: `- (BOOL)isBetweenDate:(NSDate*)beginDate andDate:(NSDate*)endDate;` – Matteo Alessani Jul 11 '11 at 08:24
  • Ha! I was just about to comment that I made a Category from this, when I noticed that Matteo's comment was hidden/collapsed from view. Good call, Matteo! – Joseph DeCarlo Aug 17 '11 at 23:46
  • Quick/Fast solution - I loved it ! +1 – sagarkothari Mar 13 '12 at 07:24
  • 2
    nice solution, you can even make it less verbose by short-circuit (and a basic De Morgan application. `return !([date compare:startDate] == NSOrderedAscending || [date compare:endDate] == NSOrderedDescending);` – Gabriele Petronella Dec 28 '12 at 15:40
  • I prefer to make this method belong to NSDate variable instead of class: - (BOOL)isBetweenDate:(NSDate *)beginDate andDate:(NSDate *)endDate { if ([self compare:beginDate] == NSOrderedAscending) return NO; if ([self compare:endDate] == NSOrderedDescending) return NO; return YES; } – Zaporozhchenko Oleksandr Sep 22 '15 at 09:43
  • An `NSParameterAssert` on `beginDate` and `endDate` is probably called for, too. Either that, or defining the behaviour… `[date compare:nil]` is specifically undefined "and may change in a future version." – Steven Fisher Jan 31 '17 at 17:32
13

For the first part, use the answer from @kperryua to construct the NSDate objects you want to compare with. From your answer to your own question, it sounds like you have that figured out.

For actually comparing the dates, I totally agree with @Tim's comment on your answer. It's more concise yet actually exactly equivalent to your code, and I'll explain why.

+ (BOOL) date:(NSDate*)date isBetweenDate:(NSDate*)beginDate andDate:(NSDate*)endDate {
    return (([date compare:beginDate] != NSOrderedAscending) && ([date compare:endDate] != NSOrderedDescending));
}

Although it may seem that the return statement must evaluate both operands of the && operator, this is actually not the case. The key is "short-circuit evaluation", which is implemented in a wide variety of programming languages, and certainly in C. Basically, the operators & and && "short circuit" if the first argument is 0 (or NO, nil, etc.), while | and || do the same if the first argument is not 0. If date comes before beginDate, the test returns NO without even needing to compare with endDate. Basically, it does the same thing as your code, but in a single statement on one line, not 5 (or 7, with whitespace).

This is intended as constructive input, since when programmers understand the way their particular programming language evaluates logical expressions, they can construct them more effectively without so much about efficiency. However, there are similar tests that would be less efficient, since not all operators short-circuit. (Indeed, most cannot short-circuit, such as numerical comparison operators.) When in doubt, it's always safe to be explicit in breaking apart your logic, but code can be much more readable when you let the language/compiler handle the little things for you.

Brock Woolf
  • 46,656
  • 50
  • 121
  • 144
Quinn Taylor
  • 44,553
  • 16
  • 113
  • 131
  • 1
    Understandable. I had no idea that Objective-C would short-circuit evaluations. I'll take your word on it that it does. Thanks for clearing that up for me. I Suppose I will mark you as correct since your answer is more concise than my own... and I learned something :) – Brock Woolf Jul 12 '09 at 05:27
  • No need to take my word for it — I advocate the principle "trust, but verify" (thanks, Ronald Reagan!) if only to satisfy my curiosity and learn the bounds of when something works and doesn't. You can verify it by doing an && of two methods that log what they return when called; you'll notice that if the first one returns 0, the second one will never be called. – Quinn Taylor Jul 12 '09 at 14:40
7

Brock Woolf version in Swift:

extension NSDate
{
    func isBetweenDates(beginDate: NSDate, endDate: NSDate) -> Bool
    {
        if self.compare(beginDate) == .OrderedAscending
        {
            return false
        }

        if self.compare(endDate) == .OrderedDescending
        {
            return false
        }

        return true
    }
}
gohnjanotis
  • 6,513
  • 6
  • 37
  • 57
ChikabuZ
  • 10,031
  • 5
  • 63
  • 86
4

If you want to know if the current date falls between two given points in time (9AM - 5PM on 7/1/09), use NSCalendar and NSDateComponents to build NSDate instances for the desired times and compare them with the current date.

If you want to know if the current date falls between these two hours every day, then you could probably go the other way. Create an NSDateComponents object with and NSCalendar and your NSDate and compare the hour components.

kperryua
  • 10,524
  • 1
  • 38
  • 24
4

This can be accomplished easily using the dates' time intervals, like so:

const NSTimeInterval i = [date timeIntervalSinceReferenceDate];
return ([startDate timeIntervalSinceReferenceDate] <= i &&
        [endDate timeIntervalSinceReferenceDate] >= i);
justin
  • 104,054
  • 14
  • 179
  • 226
4

Continuing with Quinn's and Brock´s solutions, is very nice to subclass NSDate implementation, so it can be used everywhere like this:

-(BOOL) isBetweenDate:(NSDate*)beginDate andDate:(NSDate*)endDate {
    return (([self compare:beginDate] != NSOrderedAscending) && ([self compare:endDate] != NSOrderedDescending));
}

And at any part of your code you can use it as:

[myNSDate isBetweenDate:thisNSDate andDate:thatNSDate];

(myNSDate, thisNSDate and thatNSDate are of course NSDates :)

MiQUEL
  • 3,011
  • 3
  • 19
  • 19
4

If you can target iOS 10.0+/macOS 10.12+, then use the DateInterval class.

First, create a date interval with a start and end date:

let start: Date = Date()
let middle: Date = Date()
let end: Date = Date()

let dateInterval: DateInterval = DateInterval(start: start, end: end)

Then, check if the date is in the interval by using the contains method of DateInterval:

let dateIsInInterval: Bool = dateInterval.contains(middle) // true
Bryan Luby
  • 2,527
  • 22
  • 31
3

There is better and more swifty solution for this problem.

extention Date {
    func isBetween(from startDate: Date,to endDate: Date) -> Bool {
        let result = (min(startDate, endDate) ... max(startDate, endDate)).contains(self)
        return result
    }
}

Then you can call it like this.

todayDate.isBetween(from: startDate, to: endDate)

Even you can pass date random as this extension checks which one is minimum and which one in not.

you can use it in swift 3 and above.

Rehan Ali
  • 428
  • 4
  • 8
2

With Swift 5, you can use one of the two solutions below in order to check if a date occurs between two other dates.


#1. Using DateInterval's contains(_:) method

DateInterval has a method called contains(_:). contains(_:) has the following declaration:

func contains(_ date: Date) -> Bool

Indicates whether this interval contains the given date.

The following Playground code shows how to use contains(_:) in order to check if a date occurs between two other dates:

import Foundation

let calendar = Calendar.current
let startDate = calendar.date(from: DateComponents(year: 2010, month: 11, day: 22))!
let endDate = calendar.date(from: DateComponents(year: 2015, month: 5, day: 1))!
let myDate = calendar.date(from: DateComponents(year: 2012, month: 8, day: 15))!

let dateInterval = DateInterval(start: startDate, end: endDate)
let result = dateInterval.contains(myDate)
print(result) // prints: true

#2. Using ClosedRange's contains(_:) method

ClosedRange has a method called contains(_:). contains(_:) has the following declaration:

func contains(_ element: Bound) -> Bool

Returns a Boolean value indicating whether the given element is contained within the range.

The following Playground code shows how to use contains(_:) in order to check if a date occurs between two other dates:

import Foundation

let calendar = Calendar.current
let startDate = calendar.date(from: DateComponents(year: 2010, month: 11, day: 22))!
let endDate = calendar.date(from: DateComponents(year: 2015, month: 5, day: 1))!
let myDate = calendar.date(from: DateComponents(year: 2012, month: 8, day: 15))!

let range = startDate ... endDate
let result = range.contains(myDate)
//let result = range ~= myDate // also works
print(result) // prints: true
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
-1

A better version in Swift:

@objc public class DateRange: NSObject {
    let startDate: Date
    let endDate: Date

    init(startDate: Date, endDate: Date) {
        self.startDate = startDate
        self.endDate = endDate
    }

    @objc(containsDate:)
    func contains(_ date: Date) -> Bool {
        let startDateOrder = date.compare(startDate)
        let endDateOrder = date.compare(endDate)
        let validStartDate = startDateOrder == .orderedAscending || startDateOrder == .orderedSame
        let validEndDate = endDateOrder == .orderedDescending || endDateOrder == .orderedSame
        return validStartDate && validEndDate
    }
}
TheCodingArt
  • 3,436
  • 4
  • 30
  • 53