Я пишу приложение GTD для iPhone. Для должных задач я хочу отобразить что-то как "Должный завтра" или "Должный вчера" или "Должный 18-го июля". Очевидно, я должен отобразиться "Завтра", даже если задача на расстоянии меньше чем в 24 часа (например, пользовательские проверки в 23:00 в субботу, и видит, что существует задача в воскресенье в 8:00). Так, я записал метод для получения количества дней промежуточные две даты. Вот код...
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd-HH-mm"];
NSDate *nowDate = [dateFormatter dateFromString:@"2010-01-01-15-00"];
NSDate *dueDate = [dateFormatter dateFromString:@"2010-01-02-14-00"];
NSLog(@"NSDate *nowDate = %@", nowDate);
NSLog(@"NSDate *dueDate = %@", dueDate);
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *differenceComponents = [calendar components:(NSDayCalendarUnit)
fromDate:nowDate
toDate:dueDate
options:0];
NSLog(@"Days between dates: %d", [differenceComponents day]);
... и вот вывод:
NSDate *nowDate = 2010-01-01 15:00:00 -0700
NSDate *dueDate = 2010-01-02 14:00:00 -0700
Days between dates: 0
Как Вы видите, метод возвращает неправильные результаты. Это должно было возвратиться 1 как количество дней между этими двумя днями. Что я делаю неправильно здесь?
Править: Я записал другой метод. Я не сделал обширных модульных тестов, но до сих пор это, кажется, работает:
+ (NSInteger)daysFromDate:(NSDate *)fromDate inTimeZone:(NSTimeZone *)fromTimeZone untilDate:(NSDate *)toDate inTimeZone:(NSTimeZone *)toTimeZone {
NSCalendar *calendar = [NSCalendar currentCalendar];
unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
[calendar setTimeZone:fromTimeZone];
NSDateComponents *fromDateComponents = [calendar components:unitFlags fromDate:fromDate];
[calendar setTimeZone:toTimeZone];
NSDateComponents *toDateComponents = [calendar components:unitFlags fromDate:toDate];
[calendar setTimeZone:[NSTimeZone defaultTimeZone]];
NSDate *adjustedFromDate = [calendar dateFromComponents:fromDateComponents];
NSDate *adjustedToDate = [calendar dateFromComponents:toDateComponents];
NSTimeInterval timeIntervalBetweenDates = [adjustedToDate timeIntervalSinceDate:adjustedFromDate];
NSInteger daysBetweenDates = (NSInteger)(timeIntervalBetweenDates / (60.0 * 60.0 * 24.0));
NSDateComponents *midnightBeforeFromDateComponents = [[NSDateComponents alloc] init];
[midnightBeforeFromDateComponents setYear:[fromDateComponents year]];
[midnightBeforeFromDateComponents setMonth:[fromDateComponents month]];
[midnightBeforeFromDateComponents setDay:[fromDateComponents day]];
NSDate *midnightBeforeFromDate = [calendar dateFromComponents:midnightBeforeFromDateComponents];
[midnightBeforeFromDateComponents release];
NSDate *midnightAfterFromDate = [[NSDate alloc] initWithTimeInterval:(60.0 * 60.0 * 24.0)
sinceDate:midnightBeforeFromDate];
NSTimeInterval timeIntervalBetweenToDateAndMidnightBeforeFromDate = [adjustedToDate timeIntervalSinceDate:midnightBeforeFromDate];
NSTimeInterval timeIntervalBetweenToDateAndMidnightAfterFromDate = [adjustedToDate timeIntervalSinceDate:midnightAfterFromDate];
if (timeIntervalBetweenToDateAndMidnightBeforeFromDate < 0.0) {
// toDate is before the midnight before fromDate
timeIntervalBetweenToDateAndMidnightBeforeFromDate -= daysBetweenDates * 60.0 * 60.0 * 24.0;
if (timeIntervalBetweenToDateAndMidnightBeforeFromDate < 0.0)
daysBetweenDates -= 1;
}
else if (timeIntervalBetweenToDateAndMidnightAfterFromDate >= 0.0) {
// toDate is after the midnight after fromDate
timeIntervalBetweenToDateAndMidnightAfterFromDate -= daysBetweenDates * 60.0 * 60.0 * 24.0;
if (timeIntervalBetweenToDateAndMidnightAfterFromDate >= 0.0)
daysBetweenDates += 1;
}
[midnightAfterFromDate release];
return daysBetweenDates;
}
Можно попробовать использовать rangeOfUnit:
для обнуления часов, минут и секунд из дат начала и конца.
NSCalendar *calendar = [NSCalendar currentCalendar];
NSCalendarUnit range = NSDayCalendarUnit;
NSDateComponents *comps = [[NSDateComponents alloc] init];
NSDate *start = [NSDate date];
NSDate *end;
[comps setDay:1];
[calendar rangeOfUnit:range startDate:&start interval:nil forDate:start];
end = [calendar dateByAddingComponents:comps toDate:start options:0];
В этом примере начало будет 2010-06-19 00:00:00 -0400
, конец будет 2010-06-20 00:00:00 -0400
. Я предполагаю, что это будет лучше работать с методами сравнения NSCalendar, хотя сам я это не проверял.
Если все, что вас волнует, это завтра или вчера по сравнению с определенной датой, вы можете сэкономить много работы и просто проверить, находятся ли даты друг от друга только на один календарный день.
Для этого сравните даты, чтобы определить, какая из них более ранняя, а какая более поздняя (и если они сравнивают равные, выйдите с этим результатом), затем проверьте, дает ли 1 день после более ранней даты дату с тем же годом, месяц и день месяца в качестве более поздней даты.
Если вы действительно хотите знать, сколько календарных дней прошло от одной даты до другой:
components: fromDate:
, чтобы получить год, месяц и день месяца первого числа. abs
(см. abs (3) ), чтобы взять абсолютное значение. Если они относятся к разным годам и месяцам, проверьте, относятся ли они к соседним месяцам (например, с декабря 2010 г. по январь 2011 г. или с июня 2010 г. по июль 2010 г.). Если да, добавьте количество дней в месяце более ранней даты (которое можно получить, отправив календарю сообщение rangeOfUnit: inUnit: forDate:
, передав NSDayCalendarUnit
и NSMonthCalendarUnit
соответственно) к дню месяца более поздней даты, затем сравните этот результат с днем месяца более ранней даты.
Например, сравнивая 2010-12-31 и 2011-01-01, вы сначала определяете, что они находятся в соседних месяцах, а затем прибавляете 31 (количество дней в 2010-12) к 1 (день недели). месяц 2011-01-01), затем вычтите 31 (день месяца 2010-12-31) из этой суммы. Поскольку разница равна 1, более ранняя дата на один день раньше более поздней.
При сравнении 2010-12- 30 с 2011-01- 02 , вы определили, что они находятся в соседних месяцах, а затем прибавили 31 (дни в 2010-12 годах) к 2 (день месяца 2011-01-02), затем вычтите 30 (день месяца 2010-12-30) из этой суммы. 33 минус 30 равно 3, поэтому эти даты разделены на три календарных дня.
В любом случае, я настоятельно рекомендую писать модульные тесты хотя бы для этого кода. Я обнаружил, что код обработки даты является одним из наиболее часто встречающихся ошибок, которые проявляются, скажем, два раза в год.
Вот функция, которую я использовал раньше он определен в категории на NSDate
- (int) daysToDate:(NSDate*) endDate
{
//dates needed to be reset to represent only yyyy-mm-dd to get correct number of days between two days.
NSDateFormatter *temp = [[NSDateFormatter alloc] init];
[temp setDateFormat:@"yyyy-MM-dd"];
NSDate *stDt = [temp dateFromString:[temp stringFromDate:self]];
NSDate *endDt = [temp dateFromString:[temp stringFromDate:endDate]];
[temp release];
unsigned int unitFlags = NSMonthCalendarUnit | NSDayCalendarUnit;
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comps = [gregorian components:unitFlags fromDate:stDt toDate:endDt options:0];
int days = [comps day];
[gregorian release];
return days;
}
Из документации для компонентов: fromDate: toDate: options:
:
Результат будет с потерями, если не будет запрошена достаточно маленькая единица, чтобы сохранить полную точность разницы.
Поскольку разница составляет менее одного дня, он правильно возвращает результат 0 дней.