比较两个NSDate并忽略时间组成部分

72
什么是比较两个NSDate对象最高效/推荐的方法?我想要判断这两个日期是否在同一天,而不考虑时间,并已经开始编写一些使用NSDate类中的timeIntervalSinceDate:方法的代码,并获取该值除以一天中的秒数的整数。这似乎很冗长,我觉得可能会漏掉一些明显的东西。
我正在尝试修复的代码是:
if (!([key compare:todaysDate] == NSOrderedDescending))
{
    todaysDateSection = [eventSectionsArray count] - 1;
}

其中key和todaysDate是NSDate对象,而todaysDate是使用以下方式创建的:

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

问候

Dave

16个回答

83

我很惊讶其他答案没有提到获取对象的“开始日期”的选项:

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

这段代码将date1date2设置为它们各自所在日期的开始时刻。如果它们相等,则意味着它们处于同一天。

或者选择以下代码:

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

这将设置day1day2为可以进行比较的任意值。如果它们相等,那么它们就是在同一天。


小提示,NSDayCalendarUnit已被弃用,替换为NSCalendarUnitDay - Dwayne Forde

73

在比较日期之前,将时间设置为00:00:00:

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

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

NSDate* dateOnly = [calendar dateFromComponents:components];

// ... necessary cleanup

然后您可以比较日期值。请参阅参考文档中的概述


13
我的建议是:正确的实现是黄金;然后进行剖析。如果这个循环确实成为了瓶颈,那么进行优化。 - Gregory Pakosz
3
由于你仅提取了年、月和日单位,所以时、分和秒将自动设置为0。因此,你无需显式地自己进行操作。 - Dave DeLong
1
我尝试使用这种方法,但它没有起作用。我总是得到时间不等于00:00的结果? - iosMentalist
1
我不得不使用以下代码配置日历对象 [calendar setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; 否则比较将无法正常工作。 - Vladimír Slavík
@kanecheshire 我没有详细调查这个问题。我猜这取决于手机的区域/时区设置。我宁愿使用明确的解决方案,而不是期望某些东西。 - Vladimír Slavík
显示剩余4条评论

20

iOS 8引入了一种新的方法,使得这个过程更加简单。

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

您可以将粒度设置为关注的单位。这样会忽略其他所有单位,并将比较限制在所选单位中。


这是一个非常酷的方法。(已投票)可惜它只在iOS 8中可用。我猜如果你仍然需要支持iOS <8,你可以编写一个使用我列出的iOS <8代码和compareDate:toDate:toUnitGranularity:在iOS >=8上的方法。然后,一旦你停止支持<8的OS版本,你就可以将实现压缩到只调用compareDate:toDate:toUnitGranularity: - Duncan C
1
当然,最终的方法将是 differenceBetweenDate:andDate:usingUnit:,它将返回负数、零或正整数值,表示所请求单位中日期之间的差异量。 - Duncan C
这个方法可以使用,但是关于单位参数的解释是错误的!它只期望使用最小的单位。苹果文档:必须使用最小的单位,以及所有更大的单位,才能将给定的日期视为相同。 - Matoz
例子用法:BOOL competitionStarted = [[NSCalendar currentCalendar] compareDate:self.competition.startDate toDate:[NSDate date] toUnitGranularity:NSCalendarUnitDay] != NSOrderedDescending; - Leon

17

iOS8及更高版本,检查两个日期是否在同一天的方法如下:

if NSCalendar.current.isDate(date1, inSameDayAs:date2) {
[[NSCalendar currentCalendar] isDate:date1 inSameDayAsDate:date2]

请参阅文档


这节省了我很多时间。谢谢! - Vinayak

10

这是所有答案的速记:

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
    }

1
这段代码不起作用。如果你给 [NSDate date] 加上 23 小时,它会显示两个日期在同一天,这在大多数情况下显然是错误的。 - Gottfried
@Gottfried 这将忽略时间部分,因为OP请求这样做。不确定您所说的添加23小时是什么意思。您是指更改日期吗? - Bms270
@Bms270的意思是更改日期。如果你将今天下午3点与明天上午9点进行比较,你的代码会返回0天的差距,尽管第二个日期显然是另一天。 - Christian Schnorr
@Jenox 你试过这段代码吗?你的例子应该返回:无论什么时间,今天都比明天早。它简单地忽略了小时,就像你在将12点与12点进行比较一样。OP说“不考虑时间”。 - Bms270
日历方法的整个_重点_在于它们是聪明的。该调用将两个日期映射到日历的时区,忽略所有较小的单位,并且如果您将今天凌晨2点与明天凌晨1点进行比较,则它们不在同一天,因此结果为1。 - gnasher729

6
我使用这个小工具方法:
-(NSDate*)normalizedDateWithDate:(NSDate*)date
{
   NSDateComponents* components = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit)
                                              fromDate: date];
   return [calendar_ dateFromComponents:components]; // NB calendar_ must be initialized
}

显然,您需要有一个名为calendar_的实例变量,其中包含一个NSCalendar对象。

使用此方法,可以轻松检查日期是否为今天:

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

([NSDate date] 返回当前日期和时间。)

当然,这与Gregory提出的建议非常相似。这种方法的缺点是它往往会创建大量临时的NSDate对象。如果你要处理大量的日期,我建议使用其他方法,如直接比较组件或使用NSDateComponents对象而不是NSDates


6
我使用了Duncan C的方法,修正了他所犯的一些错误。
-(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;
}

3
从iOS 8.0开始,您可以使用:
NSCalendar *calendar = [NSCalendar currentCalendar];
NSComparisonResult dateComparison = [calendar compareDate:[NSDate date] toDate:otherNSDate toUnitGranularity:NSCalendarUnitDay];

如果结果是例如NSOrderedDescending,表示otherDate比[NSDate date]早几天。
我在NSCalendar文档中没有看到这个方法,但它在iOS 7.1 to iOS 8.0 API Differences中提到。

3

针对使用Swift 3进行编码的开发人员

if(NSCalendar.current.isDate(selectedDate, inSameDayAs: NSDate() as Date)){
     // Do something
}

3

在Swift 3中,根据您的需求,您可以选择以下两种模式之一来解决问题。


#1. 使用compare(_:to:toGranularity:)方法

Calendar有一个叫做compare(_:​to:​to​Granularity:​)的方法。 compare(_:​to:​to​Granularity:​)的声明如下:

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

比较给定日期到指定的时间单元,如果它们在指定的时间单元以及所有更大的时间单元中相同,则报告它们为orderedSame,否则可以是orderedAscendingorderedDescending
下面的Playground代码展示了如何使用它:
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. 使用 dateComponents(_:from:to:)

Calendar 类有一个名为 dateComponents(_:from:to:) 的方法。该方法的声明如下:

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

返回两个日期之间的差异。

下面的 Playground 代码演示了如何使用它:

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
}

谢谢!这正是我正在寻找的,非常有帮助。 - Vishal Shelake

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