2010-06-19 21 views
5

Estoy escribiendo una aplicación GTD para el iPhone. Para las tareas vencidas, quiero mostrar algo como "Vencimiento mañana" o "Vencimiento ayer" o "Vencimiento el 18 de julio". Obviamente, tengo que mostrar "Mañana", incluso si la tarea está a menos de 24 horas de distancia (por ejemplo, el usuario revisa a las 11 pm el sábado y ve que hay una tarea el domingo a las 8 a.m.). Entonces, escribí un método para obtener la cantidad de días entre dos fechas. Aquí está el código ...iPhone - obtenga el número de días entre dos fechas

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]); 

... y aquí está la salida:

NSDate *nowDate = 2010-01-01 15:00:00 -0700 
NSDate *dueDate = 2010-01-02 14:00:00 -0700 
Days between dates: 0 

Como se puede ver, el método devuelve resultados incorrectos. Debería haber devuelto 1 como el número de días entre los dos días. ¿Qué estoy haciendo mal aquí?

EDITAR: Escribí otro método. No he hecho extensas pruebas de unidad, pero hasta ahora parece que funciona:

+ (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; 
} 

Respuesta

3

A partir de los documentos de components:fromDate:toDate:options::

El resultado es pérdida si no hay una unidad lo suficientemente pequeño solicitado a mantener la precisión total de la diferencia.

Dado que la diferencia es de menos de un día completo, devuelve correctamente un resultado de 0 días.

+0

Bueno, a mi palabra pregunta de otra manera, ¿cómo puedo comprobar cuántos medias noches había entre mis dos fechas? – Tim

2

Si todo lo que te preocupa es mañana o ayer frente a una fecha específica, entonces puedes ahorrarte mucho trabajo y solo probar si las fechas tienen un solo día calendario de diferencia.

Para hacer eso, compare las fechas para encontrar cuál es anterior y cuál es posterior (y si se comparan iguales, rescatar con ese resultado), luego pruebe si 1 día después de la fecha anterior produce una fecha con el mismo año , mes y día del mes como la fecha posterior.


Si realmente quieren saber exactamente cuántos días de calendario que hay de una fecha a la otra:

  1. Enviar un mensaje al calendario components:fromDate: para obtener el año, mes y día- del mes de la primera fecha.
  2. Igual que el # 1, pero para la segunda fecha.
  3. Si las dos fechas están en el mismo año y mes, reste un día del mes de la otra y pase al abs (vea abs(3)) para tomar el valor absoluto.
  4. Si no coinciden en el mismo año y mes, compruebe si se encuentran en meses adyacentes (p. Ej., Diciembre de 2010 a enero de 2011 o junio de 2010 a julio de 2010). Si lo son, agregue el número de días en el mes anterior (que puede obtener enviando al calendario un mensaje rangeOfUnit:inUnit:forDate:, pasando NSDayCalendarUnit y NSMonthCalendarUnit, respectivamente) al día del mes de la fecha posterior, luego compare ese resultado a la fecha anterior del día del mes.

    Por ejemplo, al comparar 2010-12-31 a 2011-01-01, primero determinaría que estos son en meses adyacentes, luego agregaría 31 (número de días en 2010-12) a 1 (día-de -mes de 2011-01-01), restar 31 (día-del-mes de 2010-12-31) de esa suma. Como la diferencia es 1, la fecha anterior es un día antes de la fecha posterior.

    Al comparar 2010-12- 30 a 02 2011-01- , que determinaría que están en meses adyacentes, a continuación, añadir 31 días (en 2010-12) a 2 (mes-día-de del 2011-01-02), luego restar 30 (día de mes de 2010-12-30) de esa suma. 33 menos 30 es 3, por lo que estas fechas tienen tres días calendario de diferencia.


De cualquier manera, le recomiendo escribir las pruebas unitarias, al menos para este código. Me he dado cuenta que el código de fecha de manipulación está entre los más propensos a tener errores sutiles que sólo se manifiesta, por ejemplo, dos veces al año.

+0

sonidos Lo que estamos sugiriendo bastante complicado. Escribí otro método, mira mi edición de la publicación original. No estoy seguro de si funciona correctamente en todos los casos, pero hasta ahora ha funcionado. – Tim

+0

Tim: Mis dos sugerencias son menos complicados que lo que escribió en su pregunta, sobre todo la primera sugerencia, que no debe ser más de una docena de estados. –

2

Una cosa que usted puede intentar es usar rangeOfUnit: a cero fuera de horas, minutos y segundos de las fechas de inicio y fin.

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]; 

En este ejemplo salida será 2010-06-19 00:00:00 -0400, final será 2010-06-20 00:00:00 -0400. Me imagino que esto funcionaría mejor con los métodos de comparación de NSCalendar, aunque yo mismo no lo he probado.

0

Aquí es la función que he usado en el pasado su definido en una categoría en 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; 
} 
2

Se puede trabajar fácilmente utilizando la respuesta muy agradable a este related question

0
-(NSInteger)daysBetweenTwoDates:(NSDate*)fromDateTime andDate:(NSDate*)toDateTime 

{

NSDate *fromDate; 
NSDate *toDate; 

NSCalendar *calendar = [NSCalendar currentCalendar]; 

[calendar rangeOfUnit:NSDayCalendarUnit startDate:&fromDate 
      interval:NULL forDate:fromDateTime]; 
[calendar rangeOfUnit:NSDayCalendarUnit startDate:&toDate 
      interval:NULL forDate:toDateTime]; 

NSDateComponents *difference = [calendar components:NSDayCalendarUnit 
              fromDate:fromDate toDate:toDate options:0]; 

return [difference day]; 

}

1

estoy usando esta pieza de código, que está funcionando muy bien:

- (NSInteger)daysToDate:(NSDate*)date 
{ 
    if(date == nil) { 
     return NSNotFound; 
    } 
    NSUInteger otherDay = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitEra forDate:date]; 
    NSUInteger currentDay = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitEra forDate:self]; 
    return (otherDay-currentDay); 
} 
Cuestiones relacionadas