如何比较两个NSDate:哪一个更近?

247

我正在尝试实现DropBox同步,并需要比较两个文件的日期。一个在我的DropBox帐户上,一个在我的iPhone上。

我想到了以下代码,但是结果不如预期。我猜我在比较这两个日期时做错了什么基本的事情。我简单地使用了 > 和 < 操作符,但我猜这样做不好,因为我在比较两个NSDate字符串。以下是代码:

NSLog(@"dB...lastModified: %@", dbObject.lastModifiedDate); 
NSLog(@"iP...lastModified: %@", [self getDateOfLocalFile:@"NoteBook.txt"]);

if ([dbObject lastModifiedDate] < [self getDateOfLocalFile:@"NoteBook.txt"]) {
    NSLog(@"...db is more up-to-date. Download in progress...");
    [self DBdownload:@"NoteBook.txt"];
    NSLog(@"Download complete.");
} else {
    NSLog(@"...iP is more up-to-date. Upload in progress...");
    [self DBupload:@"NoteBook.txt"];
    NSLog(@"Upload complete.");
}

这给了我以下(随机且错误的)输出:

2011-05-11 14:20:54.413 NotePage[6918:207] dB...lastModified: 2011-05-11 13:18:25 +0000
2011-05-11 14:20:54.414 NotePage[6918:207] iP...lastModified: 2011-05-11 13:20:48 +0000
2011-05-11 14:20:54.415 NotePage[6918:207] ...db is more up-to-date.

或这个,它碰巧是正确的:

2011-05-11 14:20:25.097 NotePage[6903:207] dB...lastModified: 2011-05-11 13:18:25 +0000
2011-05-11 14:20:25.098 NotePage[6903:207] iP...lastModified: 2011-05-11 13:19:45 +0000
2011-05-11 14:20:25.099 NotePage[6903:207] ...iP is more up-to-date.

11
以下是需要翻译的内容:Duplicates: 1 2 3 4 5 6 &c.这些重复链接是指向 Stack Overflow 网站上有关比较 NSDate 对象的问题的。 - jscs
1
@JoshCaswell 如果这是一个真正的重复问题,为什么不将它们合并?您以前做过这样的事情... - Dan Rosenstark
1
只有钻石版主可以执行合并操作,@Yar。 - jscs
13个回答

668

假设有两个日期:

NSDate *date1;
NSDate *date2;

那么以下比较将告诉您哪个更早/更晚/相同:

if ([date1 compare:date2] == NSOrderedDescending) {
    NSLog(@"date1 is later than date2");
} else if ([date1 compare:date2] == NSOrderedAscending) {
    NSLog(@"date1 is earlier than date2");
} else {
    NSLog(@"dates are the same");
}
请参考NSDate类文档获取更多细节信息。

太好了!比起搞那些[date1 earlierDate:date2]之类的东西要方便多了...谢谢 - 不知道为什么以前从来没想过使用compare:。 - SomaMan
11
我喜欢依赖于NSOrderedAscending < 0和NSOrderedDescending > 0这个事实。这使得比较更容易阅读:[date1 compare:date2] < 0 /* date1 < date2 */,并避免了(容易犯的)错误,正如@albertamg指出的那样。;-) - jpap
好的 - 比较方法和偏移量错误一样容易出错。 因此,您应该使用(NSDate *)laterDate:(NSDate *)anotherDate,它将返回两个日期中较晚的日期。 所以你只需比较你期望的结果,就完成了! 不需要纠结于“等等,降序/升序?!” - omni
@jpap 这也让我感到困惑了;似乎苹果希望你认为 date1 -> date2 表示升序或降序(因此 date1 分别是晚于或早于 date2)。 - Ja͢ck
@Jack,这是一种更抽象的方式,不需要使用魔术数字(-1、0、1)来对排序算法中的元素进行排序。你也可以重新定义这些常量,使用更易读的名称。我的答案能够完成任务,但并不是最易读的代码。向下滚动,还有其他好的答案。 - Nick Weaver
1
今天我发现了一个严重的问题,如果你比较两个相同日期但不同时间的日期,结果会有所不同。例如,如果我将今天的日期“2017-03-27 14:26:38 +0000”与“2017-03-27 10:16:14 +0000”进行NSOrderedDescending比较,结果将为TRUE,而如果我将今天的日期“2017-03-27 00:00:01 +0000”与“2017-03-27 10:07:29 +0000”进行比较,结果将为FALSE。并不是说答案是错误的,但对于想要仅比较日期的人来说,这不能使用。 - user5936834

50

虽然来晚了,但比较NSDate对象的另一种简单方法是将它们转换为原始类型,这样可以轻松使用'>' '<' '=='等运算符。

例如:

if ([dateA timeIntervalSinceReferenceDate] > [dateB timeIntervalSinceReferenceDate]) {
    //do stuff
}

timeIntervalSinceReferenceDate 将日期转换为自参考日期(2001年1月1日,GMT)以来的秒数。由于timeIntervalSinceReferenceDate返回NSTimeInterval类型(这是double typedef),因此我们可以使用原始比较器。


4
比起(NSComparisonResult)compare:(NSDate *)稍微更加直观,但对于这个简单的操作来说还是有点啰嗦...(像往常一样) - Pierre de LESPINAY
1
可以使用[dateA timeIntervalSinceDate:dateB] > 0进行比较。 - Scott Fister

15

在 Swift 中,您可以重载现有的运算符:

func > (lhs: NSDate, rhs: NSDate) -> Bool {
    return lhs.timeIntervalSinceReferenceDate > rhs.timeIntervalSinceReferenceDate
}

func < (lhs: NSDate, rhs: NSDate) -> Bool {
    return lhs.timeIntervalSinceReferenceDate < rhs.timeIntervalSinceReferenceDate
}

然后,您可以直接使用<>==(已支持)比较NSDate。


如果我尝试将此内容制作为扩展,则会出现“仅允许在全局范围内使用运算符”的错误提示。有什么建议吗? - JohnVanDijk
@JohnVanDijk 你不能把它放在扩展内部。我会把它放在与扩展相同的文件中,但是在 { ... } 外面。 - Andrew

13

NSDate有一个比较函数。

compare:返回一个NSComparisonResult值,该值指示接收者和另一个给定日期的时间顺序。

(NSComparisonResult)compare:(NSDate *)anotherDate

参数:anotherDate 要与接收器进行比较的日期。 该值不能为nil。如果该值为nil,则行为未定义,并且在Mac OS X的将来版本中可能会更改。

返回值:

  • 如果接收器和anotherDate完全相等,则为NSOrderedSame
  • 如果接收器比anotherDate晚,则为NSOrderedDescending
  • 如果接收器比anotherDate早,则为NSOrderedAscending

@Irene,有没有办法比较两个NSDate对象,其中只有时间组件不同?由于某种原因,上述方法不起作用。 - ThE uSeFuL

12

您想要使用NSDate类的compare:、laterDate:、earlierDate:或isEqualToDate:方法。在此情况下使用<和>操作符将比较指针,而不是日期。


11
- (NSDate *)earlierDate:(NSDate *)anotherDate

这将返回接收器和另一个日期中较早的那个。如果两者相同,将返回接收器。

1
请注意,在编译代码的64位版本中,NSDate的后备对象可能会被优化,以便表示相同时间的日期具有相同的地址。因此,如果cDate = [aDate earlierDate:bDate],那么cDate == aDatecDate == bDate都可以是真的。在iOS 8上进行一些日期工作时发现了这一点。 - Ben Flynn
1
相反地,在32位平台上,如果日期不同,则-earlierDate:(和-laterDate:)可能既不返回接收器也不返回参数。 - Ben Lings
实际上,我之前关于32位平台的评论是不正确的。-earlierDate:-laterDate:会返回参数或接收器。但是,在NSManagedObject上的NSDate属性每次调用都会返回不同的实例。 - Ben Lings

7

一些日期工具,包括英文比较,这很不错:

#import <Foundation/Foundation.h>


@interface NSDate (Util)

-(BOOL) isLaterThanOrEqualTo:(NSDate*)date;
-(BOOL) isEarlierThanOrEqualTo:(NSDate*)date;
-(BOOL) isLaterThan:(NSDate*)date;
-(BOOL) isEarlierThan:(NSDate*)date;
- (NSDate*) dateByAddingDays:(int)days;

@end

实现:
#import "NSDate+Util.h"


@implementation NSDate (Util)

-(BOOL) isLaterThanOrEqualTo:(NSDate*)date {
    return !([self compare:date] == NSOrderedAscending);
}

-(BOOL) isEarlierThanOrEqualTo:(NSDate*)date {
    return !([self compare:date] == NSOrderedDescending);
}
-(BOOL) isLaterThan:(NSDate*)date {
    return ([self compare:date] == NSOrderedDescending);

}
-(BOOL) isEarlierThan:(NSDate*)date {
    return ([self compare:date] == NSOrderedAscending);
}

- (NSDate *) dateByAddingDays:(int)days {
    NSDate *retVal;
    NSDateComponents *components = [[NSDateComponents alloc] init];
    [components setDay:days];

    NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    retVal = [gregorian dateByAddingComponents:components toDate:self options:0];
    return retVal;
}

@end

2
或者只需使用:github.com/erica/NSDate-Extensions - Dan Rosenstark
请注意:如果日期为 nil,则比较未定义 - Nick Weaver

6
为什么你们不使用这些NSDate比较方法:
- (NSDate *)earlierDate:(NSDate *)anotherDate;
- (NSDate *)laterDate:(NSDate *)anotherDate;

6

您应该使用:

- (NSComparisonResult)compare:(NSDate *)anotherDate

比较日期。Objective-C 中没有运算符重载。


4

我遇到了几乎相同的情况,但是在我的情况下,我正在检查天数差异

NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents *compDate = [cal components:NSDayCalendarUnit fromDate:fDate toDate:tDate options:0];
int numbersOfDaysDiff = [compDate day]+1; // do what ever comparison logic with this int.

当你需要比较NSDate在天/月/年单位时很有用


NSDayCalendarUnit已被弃用,请使用NSCalendarUnitDay代替。 - Mazen Kasser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接