iPhone - получите количество дней между двумя датами

Я пишу приложение 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;
}
5
задан Tim 19 June 2010 в 13:57
поделиться

4 ответа

Можно попробовать использовать 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, хотя сам я это не проверял.

2
ответ дан 13 December 2019 в 22:01
поделиться

Если все, что вас волнует, это завтра или вчера по сравнению с определенной датой, вы можете сэкономить много работы и просто проверить, находятся ли даты друг от друга только на один календарный день.

Для этого сравните даты, чтобы определить, какая из них более ранняя, а какая более поздняя (и если они сравнивают равные, выйдите с этим результатом), затем проверьте, дает ли 1 день после более ранней даты дату с тем же годом, месяц и день месяца в качестве более поздней даты.


Если вы действительно хотите знать, сколько календарных дней прошло от одной даты до другой:

  1. Отправьте календарю сообщение components: fromDate: , чтобы получить год, месяц и день месяца первого числа.
  2. То же, что №1, но для второго свидания.
  3. Если две даты относятся к одному и тому же году и месяцу, вычтите один день месяца из другого и перейдите к abs (см. abs (3) ), чтобы взять абсолютное значение.
  4. Если они относятся к разным годам и месяцам, проверьте, относятся ли они к соседним месяцам (например, с декабря 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, поэтому эти даты разделены на три календарных дня.


В любом случае, я настоятельно рекомендую писать модульные тесты хотя бы для этого кода. Я обнаружил, что код обработки даты является одним из наиболее часто встречающихся ошибок, которые проявляются, скажем, два раза в год.

2
ответ дан 13 December 2019 в 22:01
поделиться

Вот функция, которую я использовал раньше он определен в категории на 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;
}
0
ответ дан 13 December 2019 в 22:01
поделиться

Из документации для компонентов: fromDate: toDate: options: :

Результат будет с потерями, если не будет запрошена достаточно маленькая единица, чтобы сохранить полную точность разницы.

Поскольку разница составляет менее одного дня, он правильно возвращает результат 0 дней.

3
ответ дан 13 December 2019 в 22:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: