Vergleichen von zwei NSDates und Ignorieren der Zeitkomponente

72

Was ist der effizienteste / empfohlene Weg, um zwei NSDates zu vergleichen? Ich möchte in der Lage sein zu sehen, ob beide Daten unabhängig von der Uhrzeit am selben Tag liegen, und habe begonnen, Code zu schreiben, der die timeIntervalSinceDate: -Methode innerhalb der NSDate-Klasse verwendet und die Ganzzahl dieses Werts geteilt durch die Anzahl der Sekunden erhält in einem Tag. Das scheint langwierig und ich habe das Gefühl, dass mir etwas Offensichtliches fehlt.

Der Code, den ich zu beheben versuche, lautet:

if (!([key compare:todaysDate] == NSOrderedDescending))
{
    todaysDateSection = [eventSectionsArray count] - 1;
}

Dabei sind key und todaysDate NSDate-Objekte und todaysDate wird erstellt mit:

NSDate *todaysDate = [[NSDate alloc] init];

Grüße

Dave

Zauberkugel Dave
quelle
schöne Frage +1
Abbood

Antworten:

73

Sie stellen die Uhrzeit im Datum auf 00:00:00 ein, bevor Sie den Vergleich durchführen:

unsigned int flags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
NSCalendar* calendar = [NSCalendar currentCalendar];

NSDateComponents* components = [calendar components:flags fromDate:date];

NSDate* dateOnly = [calendar dateFromComponents:components];

// ... necessary cleanup

Dann können Sie die Datumswerte vergleichen. Siehe die Übersicht in der Referenzdokumentation .

Gregory Pakosz
quelle
Vielen Dank für die schnelle Antwort Gregory, das ist wirklich hilfreich. Ich denke, meine nächste Frage ist, da dies Teil einer engen Schleife ist, denken Sie, dass es effizienter ist, die timeIntervalSinceDate: -Methode und eine ganzzahlige Arithmetik zu verwenden, anstatt mehr Objekte zu erstellen, die erforderliche Berechnung durchzuführen und dann aufzuräumen? Nochmals vielen Dank für Ihre Hilfe, Dave
Magic Bullet Dave
13
Mein Rat ist: Richtige Implementierung ist Gold; dann Profil. Wenn diese bestimmte Schleife wirklich ein Engpass ist, dann optimieren Sie
Gregory Pakosz
3
Da Sie nur die Einheiten Jahr, Monat und Tag herausziehen, werden Stunde, Minuten und Sekunden automatisch auf 0 gesetzt. Daher müssen Sie dies nicht explizit selbst tun.
Dave DeLong
1
Ich habe versucht, diese Methode zu verwenden, hat nicht funktioniert. Ich habe immer die Zeit ungleich 00:00?
iosMentalist
1
Ich musste das Kalenderobjekt mit [calendar setTimeZone: [NSTimeZone timeZoneWithAbbreviation: @ "GMT"]] konfigurieren. Andernfalls funktioniert der Vergleich nicht ordnungsgemäß.
Vladimír Slavík
83

Ich bin überrascht, dass keine anderen Antworten diese Option haben, um das "Beginn des Tages" -Datum für die Objekte zu erhalten:

[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay startDate:&date1 interval:NULL forDate:date1];
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay startDate:&date2 interval:NULL forDate:date2];

Welche setzt date1und date2an den Anfang ihrer jeweiligen Tage. Wenn sie gleich sind, sind sie am selben Tag.

Oder diese Option:

NSUInteger day1 = [[NSCalendar currentCalendar] ordinalityOfUnit:NSDayCalendarUnit inUnit: forDate:date1];
NSUInteger day2 = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitEra forDate:date2];

Welche setzt day1und day2auf etwas willkürliche Werte, die verglichen werden können. Wenn sie gleich sind, sind sie am selben Tag.

Ed Marty
quelle
Kleine FYI, NSDayCalendarUnitwurde veraltet und wird ersetzt durchNSCalendarUnitDay
Dwayne Forde
20

Es gibt eine neue Methode, die mit iOS 8 in NSCalendar eingeführt wurde und dies erheblich vereinfacht.

- (NSComparisonResult)compareDate:(NSDate *)date1 toDate:(NSDate *)date2 toUnitGranularity:(NSCalendarUnit)unit NS_AVAILABLE(10_9, 8_0);

Sie legen die Granularität auf die Einheit (en) fest, die wichtig sind. Dies ignoriert alle anderen Einheiten und schränkt den Vergleich mit den ausgewählten ein.

static0886
quelle
Das ist eine sehr coole Methode. (abgestimmt) Schade, dass es nur in iOS 8 verfügbar ist. Ich denke, wenn Sie iOS <8 noch unterstützen müssen, können Sie eine Methode mit dem Code schreiben, den ich für iOS <8 und compareDate:toDate:toUnitGranularity:unter iOS> = 8 aufgelistet habe . Sobald Sie die Unterstützung für Betriebssystemversionen <8 einstellen, können Sie die Implementierung auf den Aufruf von reduzieren compareDate:toDate:toUnitGranularity:.
Duncan C
1
Die ultimative Methode wäre differenceBetweenDate:andDate:usingUnit:natürlich, negative, null oder positive ganzzahlige Werte zurückzugeben, die den Betrag der Differenz zwischen den Daten in der angeforderten Uint angeben.
Duncan C
Diese Methode kann verwendet werden, aber die Erklärung für den Einheitenparameter ist falsch! Es wird nur die kleinste Einheit erwartet, die verwendet werden sollte. Apple-Dokumentation: Die kleinste Einheit, die zusammen mit allen größeren Einheiten gleich sein muss, damit die angegebenen Daten als gleich gelten
Matoz
Anwendungsbeispiel: BOOL CompetitionStarted = [[NSCalendar currentCalendar] compareDate: self.competition.startDate toDate: [NSDate date] toUnitGranularity: NSCalendarUnitDay]! = NSOrderedDescending;
Leon
16

Für iOS8 und höher ist die Überprüfung, ob zwei Daten am selben Tag auftreten, so einfach wie:

[[NSCalendar currentCalendar] isDate:date1 inSameDayAsDate:date2]

Siehe Dokumentation

James
quelle
Das hat mir viel Zeit gespart. Vielen Dank!
Vinayak
10

Dies ist eine Abkürzung aller Antworten:

NSInteger interval = [[[NSCalendar currentCalendar] components: NSDayCalendarUnit
                                                                  fromDate: date1
                                                                    toDate: date2
                                                                   options: 0] day];
    if(interval<0){
       //date1<date2
    }else if (interval>0){
       //date2<date1
    }else{
       //date1=date2
    }
Bms270
quelle
1
Dieser Code funktioniert nicht. Wenn Sie dem [NSDate-Datum] 23 Stunden hinzufügen, werden beide Daten am selben Tag angezeigt, was in den meisten Fällen offensichtlich falsch ist.
Gottfried
@ Gottfired Dies ignoriert den Zeitteil, wie vom OP angefordert. Ich bin mir nicht sicher, was du mit 23 Stunden meinst. Du meinst die Daten ändern?
Bms270
@ Bms270 Er meint den Tag zu ändern. Wenn Sie heute um 15:00 Uhr mit morgen um 9:00 Uhr vergleichen, gibt Ihr Code ein Delta von 0 Tagen zurück, obwohl das zweite Datum eindeutig ein anderer Tag ist.
Christian Schnorr
@ Jenox Hast du diesen Code ausprobiert? Ihr Beispiel sollte zurückkehren: heute> morgen, egal wie spät es ist. Die Stunden werden einfach ignoriert, als würden Sie 12 Uhr mit 12 Uhr vergleichen. OP sagt "unabhängig von der Zeit".
Bms270
Der springende Punkt bei den Kalendermethoden ist, dass sie clever sind. Der Anruf ordnet beide Daten der Zeitzone des Kalenders zu, ignoriert alle kleineren Einheiten und wenn Sie heute 2 Uhr morgens mit 1 Uhr morgens vergleichen, sind sie nicht am selben Tag, daher ist das Ergebnis 1.
gnasher729
6

Ich habe den Duncan C-Ansatz verwendet und einige Fehler behoben, die er gemacht hat

-(NSInteger) daysBetweenDate:(NSDate *)firstDate andDate:(NSDate *)secondDate { 

    NSCalendar *currentCalendar = [NSCalendar currentCalendar];
    NSDateComponents *components = [currentCalendar components: NSDayCalendarUnit fromDate: firstDate toDate: secondDate options: 0];

    NSInteger days = [components day];

    return days;
}
jcesarmobile
quelle
6

Ich benutze diese kleine util-Methode:

-(NSDate*)normalizedDateWithDate:(NSDate*)date
{
   NSDateComponents* components = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit)
                                              fromDate: date];
   return [calendar_ dateFromComponents:components]; // NB calendar_ must be initialized
}

(Sie benötigen offensichtlich einen Ivar calendar_mit einem NSCalendar.)

Auf diese Weise können Sie leicht überprüfen, ob ein Datum heute so ist:

[[self normalizeDate:aDate] isEqualToDate:[self normalizeDate:[NSDate date]]];

( [NSDate date]Gibt das aktuelle Datum und die aktuelle Uhrzeit zurück.)

Dies ist natürlich sehr ähnlich zu dem, was Gregory vorschlägt. Der Nachteil dieses Ansatzes besteht darin, dass er dazu neigt, viele temporäre NSDateObjekte zu erstellen . Wenn Sie viele Daten verarbeiten möchten, würde ich empfehlen, eine andere Methode zu verwenden, z. B. die Komponenten direkt zu vergleichen oder NSDateComponentsstattdessen mit Objekten zu arbeiten NSDates.

Felixyz
quelle
3

Die Antwort ist einfacher als jeder denkt. NSCalendar hat eine Methode

components:fromDate:toDate:options

Mit dieser Methode können Sie die Differenz zwischen zwei Daten mit den gewünschten Einheiten berechnen.

Schreiben Sie also eine Methode wie diese:

-(NSInteger) daysBetweenDate: (NSDate *firstDate) andDate: (NSDate *secondDate)
{
  NSCalendar *currentCalendar = [NSCalendar currentCalendar];
  NSDateComponents components* = [currentCalendar components: NSDayCalendarUnit
    fromDate: firstDate 
    toDate: secondDate
    options: 0];

  NSInteger days = [components days];
  return days;
}

Wenn die obige Methode Null zurückgibt, befinden sich die beiden Daten am selben Tag.

Duncan C.
quelle
3

Ab iOS 8.0 können Sie Folgendes verwenden:

NSCalendar *calendar = [NSCalendar currentCalendar];
NSComparisonResult dateComparison = [calendar compareDate:[NSDate date] toDate:otherNSDate toUnitGranularity:NSCalendarUnitDay];

Wenn das Ergebnis zB NSOrderedDescending ist, liegt otherDate in Tagen vor [NSDate date].

Ich sehe diese Methode nicht in der NSCalendar-Dokumentation, aber in den API-Unterschieden zwischen iOS 7.1 und iOS 8.0

Arjan
quelle
3

Mit Swift 3 können Sie je nach Bedarf eines der beiden folgenden Muster auswählen, um Ihr Problem zu lösen.


# 1. Mit compare(_:to:toGranularity:)Methode

Calendarhat eine Methode namens compare(_:​to:​to​Granularity:​). compare(_:​to:​to​Granularity:​)hat die folgende Erklärung:

func compare(_ date1: Date, to date2: Date, toGranularity component: Calendar.Component) -> ComparisonResult

Vergleicht die angegebenen Daten mit der angegebenen Komponente und gibt sie an, ordered​Samewenn sie in der angegebenen Komponente und allen größeren Komponenten identisch sind, andernfalls entweder ordered​Ascendingoder ordered​Descending.

Der folgende Spielplatzcode zeigt, wie heiß es ist, ihn zu verwenden:

import Foundation

let calendar = Calendar.current
let date1 = Date() // "Mar 31, 2017, 2:01 PM"
let date2 = calendar.date(byAdding: .day, value: -1, to: date1)! // "Mar 30, 2017, 2:01 PM"
let date3 = calendar.date(byAdding: .hour, value: 1, to: date1)! // "Mar 31, 2017, 3:01 PM"

/* Compare date1 and date2 */
do {
    let comparisonResult = calendar.compare(date1, to: date2, toGranularity: .day)
    switch comparisonResult {
    case ComparisonResult.orderedSame:
        print("Same day")
    default:
        print("Not the same day")
    }
    // Prints: "Not the same day"
}

/* Compare date1 and date3 */
do {
    let comparisonResult = calendar.compare(date1, to: date3, toGranularity: .day)
    if case ComparisonResult.orderedSame = comparisonResult {
        print("Same day")
    } else {
        print("Not the same day")
    }
    // Prints: "Same day"
}

# 2. Verwenden vondateComponents(_:from:to:)

Calendarhat eine Methode namens dateComponents(_:from:to:). dateComponents(_:from:to:)hat die folgende Erklärung:

func dateComponents(_ components: Set<Calendar.Component>, from start: Date, to end: Date) -> DateComponents

Gibt die Differenz zwischen zwei Daten zurück.

Der folgende Spielplatzcode zeigt, wie heiß es ist, ihn zu verwenden:

import Foundation

let calendar = Calendar.current
let date1 = Date() // "Mar 31, 2017, 2:01 PM"
let date2 = calendar.date(byAdding: .day, value: -1, to: date1)! // "Mar 30, 2017, 2:01 PM"
let date3 = calendar.date(byAdding: .hour, value: 1, to: date1)! // "Mar 31, 2017, 3:01 PM"

/* Compare date1 and date2 */
do {
    let dateComponents = calendar.dateComponents([.day], from: date1, to: date2)
    switch dateComponents.day {
    case let value? where value < 0:
        print("date2 is before date1")
    case let value? where value > 0:
        print("date2 is after date1")
    case let value? where value == 0:
        print("date2 equals date1")
    default:
        print("Could not compare dates")
    }
    // Prints: date2 is before date1
}

/* Compare date1 and date3 */
do {
    let dateComponents = calendar.dateComponents([.day], from: date1, to: date3)
    switch dateComponents.day {
    case let value? where value < 0:
        print("date2 is before date1")
    case let value? where value > 0:
        print("date2 is after date1")
    case let value? where value == 0:
        print("date2 equals date1")
    default:
        print("Could not compare dates")
    }
    // Prints: date2 equals date1
}
Imanou Petit
quelle
Vielen Dank!! das, wonach ich suche Sehr hilfreich
Vishal Shelake
2

Für Entwickler, die in Swift 3 codieren

if(NSCalendar.current.isDate(selectedDate, inSameDayAs: NSDate() as Date)){
     // Do something
}
jo3birdtalk
quelle
1
int interval = (int)[firstTime timeIntervalSinceDate:secondTime]/(60*60*24);
if (interval!=0){
   //not the same day;
}
ChihHao
quelle
1

Meine Lösung waren zwei Konvertierungen mit NSDateFormatter:

    NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
    [dateFormat setDateFormat:@"yyyyMMdd"];
    [dateFormat setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]];

    NSDate *today = [NSDate dateWithTimeIntervalSinceNow:0];
    NSString *todayString=[dateFormat stringFromDate:today];
    NSDate *todayWithoutHour=[dateFormat dateFromString:todayString];

    if ([today compare:exprDate] == NSOrderedDescending)
    {
       //do  
    }
Zaster
quelle
0

Die Dokumentation in Bezug auf NSDate zeigt an, dass die compare:und isEqual:Methoden sowohl einen grundlegenden Vergleich durchführen und die Ergebnisse zu bestellen, wenn auch sie noch Faktor in der Zeit.

Der wahrscheinlich einfachste Weg, die Aufgabe zu verwalten, besteht darin, eine neue isTodayMethode zu erstellen, die Folgendes bewirkt:

- (bool)isToday:(NSDate *)otherDate
{
    currentTime = [however current time is retrieved]; // Pardon the bit of pseudo-code

    if (currentTime < [otherDate timeIntervalSinceNow])
    {
        return YES;
    }
    else
    {
        return NO;
    }
}
Kaji
quelle
0

Dies ist eine besonders hässliche Katze für die Haut, aber hier ist eine andere Möglichkeit, dies zu tun. Ich sage nicht, dass es elegant ist, aber es ist wahrscheinlich so nah wie möglich an der Datums- / Zeitunterstützung in iOS.

bool isToday = [[NSDateFormatter localizedStringFromDate:date dateStyle:NSDateFormatterFullStyle timeStyle:NSDateFormatterNoStyle] isEqualToString:[NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterFullStyle timeStyle:NSDateFormatterNoStyle]];
Scott bedeutet
quelle
0
NSUInteger unit = NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit;
NSDateComponents *comp = [cal components:unit
                                fromDate:nowDate
                                  toDate:setDate
                                 options:0];

NSString *dMonth;
dMonth = [NSString stringWithFormat:@"%02ld",comp.month];
NSString *dDay;
dDay = [NSString stringWithFormat:@"%02ld",comp.day + (comp.hour > 0 ? 1 : 0)];

Vergleichen Sie auch die Stunde, um den Unterschied von 1 Tag zu beheben

Minho Lee
quelle