Сравнение двух NSDates и игнорирование временной составляющей
-
13-09-2019 - |
Вопрос
Каков наиболее эффективный/рекомендуемый способ сравнения двух NSDates?Я хотел бы видеть, совпадают ли обе даты в один и тот же день, независимо от времени, и начал писать код, использующий timeIntervalSinceDate:внутри класса NSDate и получает целое число этого значения, деленное на количество секунд в дне.Это кажется многословным, и я чувствую, что упускаю что-то очевидное.
Код, который я пытаюсь исправить:
if (!([key compare:todaysDate] == NSOrderedDescending))
{
todaysDateSection = [eventSectionsArray count] - 1;
}
где ключ и TodaysDate являются объектами NSDate, а TodaysDate создается с использованием:
NSDate *todaysDate = [[NSDate alloc] init];
С уважением
Дэйв
Решение
Перед сравнением вы устанавливаете время в дате на 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
Затем вы можете сравнить значения дат.См. обзор в справочной документации.
Другие советы
Я удивлен, что ни в одном другом ответе нет такой возможности для получения даты «начала дня» для объектов:
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay startDate:&date1 interval:NULL forDate:date1];
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay startDate:&date2 interval:NULL forDate:date2];
Какие наборы date1
и date2
к началу своих дней.Если они равны, то они в один и тот же день.
Или этот вариант:
NSUInteger day1 = [[NSCalendar currentCalendar] ordinalityOfUnit:NSDayCalendarUnit inUnit: forDate:date1];
NSUInteger day2 = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitEra forDate:date2];
Какие наборы day1
и day2
к несколько произвольным значениям, которые можно сравнивать.Если они равны, то они в один и тот же день.
В NSCalendar с iOS 8 появился новый метод, который значительно упрощает эту задачу.
- (NSComparisonResult)compareDate:(NSDate *)date1 toDate:(NSDate *)date2 toUnitGranularity:(NSCalendarUnit)unit NS_AVAILABLE(10_9, 8_0);
Вы устанавливаете степень детализации для важных единиц.При этом все остальные единицы измерения игнорируются и ограничивается сравнением с выбранными.
Для iOS8 и более поздних версий проверить, совпадают ли две даты в один и тот же день, так же просто, как:
[[NSCalendar currentCalendar] isDate:date1 inSameDayAsDate:date2]
Видеть документация
Это краткое изложение всех ответов:
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
}
Я использовал подход Дункана С и исправил некоторые допущенные им ошибки.
-(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;
}
Я использую этот небольшой метод:
-(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]
возвращает текущую дату и время.)
Это, конечно, очень похоже на то, что предлагает Грегори.Недостаток этого подхода в том, что он имеет тенденцию создавать множество временных NSDate
объекты.Если вы собираетесь обрабатывать много дат, я бы рекомендовал использовать какой-нибудь другой метод, например, напрямую сравнивать компоненты или работать с NSDateComponents
объекты вместо NSDates
.
Ответ проще, чем все думают.В NSCalendar есть метод
components:fromDate:toDate:options
Этот метод позволяет вам рассчитать разницу между двумя датами, используя любые единицы, которые вы хотите.
Итак, напишите такой метод:
-(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;
}
Если приведенный выше метод возвращает ноль, две даты относятся к одному и тому же дню.
Начиная с iOS 8.0, вы можете использовать:
NSCalendar *calendar = [NSCalendar currentCalendar];
NSComparisonResult dateComparison = [calendar compareDate:[NSDate date] toDate:otherNSDate toUnitGranularity:NSCalendarUnitDay];
Если результат, например,NSOrderedDescending,otherDate предшествует [NSDate date] в днях.
Я не вижу этого метода в документации NSCalendar, но он есть в Различия API iOS 7.1 и iOS 8.0
Для разработчиков, пишущих на Swift 3
if(NSCalendar.current.isDate(selectedDate, inSameDayAs: NSDate() as Date)){
// Do something
}
В Swift 3, в соответствии с вашими потребностями, вы можете выбрать один из двух следующих шаблонов для решения вашей проблемы.
№1.С использованием compare(_:to:toGranularity:)
метод
Calendar
есть метод под названием compare(_:to:toGranularity:)
. compare(_:to:toGranularity:)
имеет следующее объявление:
func compare(_ date1: Date, to date2: Date, toGranularity component: Calendar.Component) -> ComparisonResult
Сравнивает заданные даты с данным компонентом и сообщает о них.
orderedSame
если они одинаковы в данном компоненте и во всех более крупных компонентах, в противном случае либоorderedAscending
илиorderedDescending
.
Код игровой площадки ниже показывает, что его можно использовать:
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:)
. dateComponents(_:from:to:)
имеет следующее объявление:
func dateComponents(_ components: Set<Calendar.Component>, from start: Date, to end: Date) -> DateComponents
Возвращает разницу между двумя датами.
Код игровой площадки ниже показывает, что его можно использовать:
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
}
int interval = (int)[firstTime timeIntervalSinceDate:secondTime]/(60*60*24);
if (interval!=0){
//not the same day;
}
моим решением было два преобразования с помощью 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
}
Документация относительно НСДата указывает на то, что compare:
и isEqual:
методы будут выполнять базовое сравнение и упорядочивать результаты, хотя они по-прежнему учитывают время.
Вероятно, самый простой способ справиться с задачей — создать новую isToday
метод, обеспечивающий следующее:
- (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;
}
}
Сдирать шкуру с этой кошки особенно некрасиво, но есть еще один способ сделать это.Я не говорю, что это элегантно, но, вероятно, это максимально близко к поддержке даты и времени в iOS.
bool isToday = [[NSDateFormatter localizedStringFromDate:date dateStyle:NSDateFormatterFullStyle timeStyle:NSDateFormatterNoStyle] isEqualToString:[NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterFullStyle timeStyle:NSDateFormatterNoStyle]];
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)];
сравните также час, чтобы исправить разницу в 1 день