Como verificar se um NSDate ocorre entre dois outros NSDates

90

Estou tentando descobrir se a data atual cai ou não em um intervalo de datas usando NSDate.

Por exemplo, você pode obter a data / hora atual usando NSDate:

NSDate rightNow = [NSDate date];

Eu gostaria então de usar essa data para verificar se ela está entre 9h e 17h .

Brock Woolf
fonte
1
Você pode dar uma olhada nas respostas a esta pergunta para obter mais informações sobre como criar objetos NSDate para um período específico, como das 17h às 21h.
Marc Charbonneau

Respostas:

167

Eu encontrei uma solução. Se você tiver uma solução melhor, fique à vontade para deixá-la e irei marcá-la como correta.

+ (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
fonte
3
Parece bom, mas você pode querer renomeá-lo da seguinte forma: + (BOOL) data: (NSDate *) data isBetweenDate: (NSDate *) beginDate andDate: (NSDate *) endDate para evitar confusão.
Jeff Kelley
3
Código de golfe! O compilador deve causar um curto-circuito na comparação, tornando a eficiência aproximadamente igual ao que você tem: return (([data compare: beginDate]! = NSOrderedDescending) && ([date compare: endDate]! = NSOrderedAscending));
Tim
1
Melhor ainda, eu faria disso um método de instância (uma adição a NSDate), não um método de classe. Eu iria com isto: -isBetweenDate: andDate:
Dave Batton
4
Excelente! Fiz uma categoria a partir disso:- (BOOL)isBetweenDate:(NSDate*)beginDate andDate:(NSDate*)endDate;
Matteo Alessani
2
boa solução, você pode até torná-la menos prolixa por curto-circuito (e um aplicativo De Morgan básico.return !([date compare:startDate] == NSOrderedAscending || [date compare:endDate] == NSOrderedDescending);
Gabriele Petronella
13

Para a primeira parte, use a resposta de @kperryua para construir os objetos NSDate com os quais deseja comparar. Com base na sua resposta à sua própria pergunta, parece que você já descobriu isso.

Para realmente comparar as datas, concordo totalmente com o comentário de @Tim sobre sua resposta. É mais conciso, mas na verdade exatamente equivalente ao seu código, e explicarei por quê.

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

Embora possa parecer que a instrução return deve avaliar ambos os operandos do operador &&, este não é realmente o caso. A chave é " avaliação de curto-circuito ", que é implementada em uma ampla variedade de linguagens de programação, e certamente em C. Basicamente, os operadores &e &&"curto-circuito" se o primeiro argumento for 0 (ou NÃO, nulo, etc.) , while |e ||faça o mesmo se o primeiro argumento não for 0. Se datevier antes beginDate, o teste retorna NOsem nem mesmo precisar comparar com endDate. Basicamente, ele faz a mesma coisa que seu código, mas em uma única instrução em uma linha, não 5 (ou 7, com espaço em branco).

A intenção é ser uma entrada construtiva, pois, quando os programadores entendem a maneira como sua linguagem de programação particular avalia as expressões lógicas, eles podem construí-las de maneira mais eficaz, sem tanto esforço. No entanto, existem testes semelhantes que seriam menos eficientes, uma vez que nem todos os operadores entram em curto-circuito. (Na verdade, a maioria não pode entrar em curto-circuito, como operadores de comparação numérica.) Em caso de dúvida, é sempre seguro ser explícito ao quebrar sua lógica, mas o código pode ser muito mais legível quando você deixa a linguagem / compilador lidar com as pequenas coisas para voce.

Quinn Taylor
fonte
1
Compreensível. Eu não tinha ideia de que Objective-C causaria um curto-circuito nas avaliações. Vou acreditar na sua palavra de que sim. Obrigado por esclarecer isso para mim. Suponho que vou marcá-lo como correto, já que sua resposta é mais concisa do que a minha ... e eu aprendi algo :)
Brock Woolf
Não há necessidade de acreditar na minha palavra - eu defendo o princípio "confie, mas verifique" (obrigado, Ronald Reagan!) Apenas para satisfazer minha curiosidade e aprender os limites de quando algo funciona e não funciona. Você pode verificar isso fazendo um && de dois métodos que registram o que eles retornam quando chamados; você notará que se o primeiro retornar 0, o segundo nunca será chamado.
Quinn Taylor
7

Versão de Brock Woolf em 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
    }
}
ChikabuZ
fonte
4

Se você quiser saber se a data atual cai entre dois pontos dados no tempo (9h - 17h em 01/07/09), use NSCalendar e NSDateComponents para construir instâncias NSDate para os horários desejados e compará-los com a data atual.

Se você quiser saber se a data atual cai entre essas duas horas todos os dias, provavelmente você poderia ir de outra maneira. Crie um objeto NSDateComponents com e NSCalendar e seu NSDate e compare os componentes de hora.

kperryua
fonte
4

Se você puder direcionar o iOS 10.0 + / macOS 10.12+, use a DateIntervalclasse.

Primeiro, crie um intervalo de datas com datas de início e término:

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

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

Em seguida, verifique se a data está no intervalo usando o containsmétodo de DateInterval:

let dateIsInInterval: Bool = dateInterval.contains(middle) // true
Bryan Luby
fonte
3

Isso pode ser feito facilmente usando os intervalos de tempo das datas, como:

const NSTimeInterval i = [date timeIntervalSinceReferenceDate];
return ([startDate timeIntervalSinceReferenceDate] <= i &&
        [endDate timeIntervalSinceReferenceDate] >= i);
justin
fonte
3

Continuando com as soluções de Quinn e Brock, é muito bom implementar a subclasse NSDate, então ela pode ser usada em qualquer lugar assim:

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

E em qualquer parte do seu código, você pode usá-lo como:

[myNSDate isBetweenDate:thisNSDate andDate:thatNSDate];

(myNSDate, thisNSDate e thatNSDate são, obviamente, NSDates :)

MiQUEL
fonte
3

Há uma solução melhor e mais rápida para esse problema.

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

Então você pode chamá-lo assim.

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

Até mesmo você pode passar datas aleatórias, já que esta extensão verifica qual é o mínimo e qual não é.

você pode usá-lo no swift 3 e superior.

Rehan Ali
fonte
2

Com o Swift 5, você pode usar uma das duas soluções abaixo para verificar se uma data ocorre entre duas outras datas.


# 1. Usando DateIntervalo contains(_:)método de

DateIntervaltem um método chamado contains(_:). contains(_:)tem a seguinte declaração:

func contains(_ date: Date) -> Bool

Indica se este intervalo contém a data fornecida.

O seguinte código Playground mostra como usar contains(_:)para verificar se uma data ocorre entre duas outras datas:

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. Usando ClosedRangeo contains(_:)método de

ClosedRangetem um método chamado contains(_:). contains(_:)tem a seguinte declaração:

func contains(_ element: Bound) -> Bool

Retorna um valor booleano indicando se o elemento fornecido está contido no intervalo.

O seguinte código Playground mostra como usar contains(_:)para verificar se uma data ocorre entre duas outras datas:

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
fonte
-1

Uma versão melhor em 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
fonte