¿Cómo determinar si un NSDate es hoy?

156

¿Cómo verificar si un NSDatepertenece a hoy?

Solía ​​comprobarlo usando los primeros 10 caracteres de [aDate description]. [[aDate description] substringToIndex:10]devuelve una cadena como "YYYY-MM-DD"así, comparé la cadena con la cadena devuelta por [[[NSDate date] description] substringToIndex:10].

¿Hay alguna forma más rápida y / o ordenada de verificar?

Gracias.

Seunghoon
fuente
Creo que mi método es el único (al momento de escribir esto) que en realidad se refiere a la documentación de Apple y hace lo que sugieren dentro de él para determinar si dos días son iguales. Aunque otros métodos pueden funcionar, creo que mi método es la prueba más futura. stackoverflow.com/a/17641925/901641
ArtOfWarfare
@ArtOfWarfare: no estoy de acuerdo. Su código todavía tiene que lidiar con intervalos de tiempo, pero Apple proporciona métodos que ya este factor de distancia - SEE: stackoverflow.com/a/17642033/106435
vikingosegundo
1
El método de "descripción" NSDate muestra la fecha en formato UTC, es decir, la hora del meridiano de Greenwich, sin corrección para el horario de verano. Si llega tarde a la noche en los EE. UU. O temprano en la mañana en la India, no mostrará lo que es "hoy" de acuerdo con su calendario. Necesita un objeto NSCalendar para traducir el NSDate en un día, y convertir la fecha actual en un día, luego compararlos.
gnasher729

Respuestas:

308

En macOS 10.9+ y iOS 8+, ¡hay un método en NSCalendar / Calendar que hace exactamente esto!

- (BOOL)isDateInToday:(NSDate *)date 

Entonces simplemente harías

C objetivo:

BOOL today = [[NSCalendar currentCalendar] isDateInToday:date];

Swift 3:

let today = Calendar.current.isDateInToday(date)
Catfish_Man
fuente
1
Verifique usando el SDK de iOS 8
Catfish_Man
1
Estoy confundido por el comentario anterior, este es definitivamente iOS 8: - (BOOL) isDateInToday: (NSDate *) date NS_AVAILABLE (10_9, 8_0);
powerj1984
Sí, eso es lo que dije. El comentarista anterior estaba confundido al mirar el SDK de iOS 7, presumiblemente. Tenía una declaración diferente.
Catfish_Man
para 'hoy' esta es la respuesta perfecta :)
Daij-Djan
Excelente respuesta!
Supertecnoboff
192

Puede comparar componentes de fecha:

NSDateComponents *otherDay = [[NSCalendar currentCalendar] components:NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:aDate];
NSDateComponents *today = [[NSCalendar currentCalendar] components:NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:[NSDate date]];
if([today day] == [otherDay day] &&
   [today month] == [otherDay month] &&
   [today year] == [otherDay year] &&
   [today era] == [otherDay era]) {
    //do stuff
}

Editar:

Me gusta más el método de stefan, creo que es una declaración if más limpia y más comprensible:

NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents *components = [cal components:(NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:[NSDate date]];
NSDate *today = [cal dateFromComponents:components];
components = [cal components:(NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:aDate];
NSDate *otherDate = [cal dateFromComponents:components];

if([today isEqualToDate:otherDate]) {
    //do stuff
}

Chris, he incorporado tu sugerencia. Tuve que buscar qué era, así que para cualquier otra persona que no sepa, distingue entre BC y AD. Esto es probablemente innecesario para la mayoría de las personas, pero es fácil de verificar y agrega cierta certeza, así que lo he incluido. Si buscas velocidad, probablemente este no sea un buen método de todos modos.


NOTA como con muchas respuestas en SO, después de 7 años esto está totalmente desactualizado. En Swift ahora solo usa.isDateInToday

David Kanarek
fuente
1
Gracias. Creo que es mi solución más clara y segura para el futuro.
Seunghoon
1
Creo que también debes incluir el componente era. Creo que es un lanzamiento de moneda entre los dos enfoques; isEqualToDate parece más agradable, pero parece un poco derrochador y toma aproximadamente el mismo número de líneas para reconstruir una fecha a partir de los componentes.
Chris Page
1
Dado que tanto "otherDay" como "today" solo tienen esos cuatro campos rellenados de todos modos (eso es todo lo que solicitó), puede usar [today equalTo: otherDay], sin saltar los aros en el segundo.
Glenn Maynard
@Glenn ¿Estás sugiriendo que compare NSDateComponents en el primero? Realmente no había pensado en eso, pero también parece una buena idea.
David Kanarek
1
@DavidKanarek Desenterrando publicaciones antiguas ... No recomendaría comparar los componentes de la fecha directamente, si se encuentra en una situación en la que necesita compensar uno de los componentes, puede obtener algo como mes == 3, día == 34. La conversión a fechas interpretará correctamente esto como el 3 de abril, al comparar los componentes de fecha no verá esto como el mes == 4, día == 3.
Sean Kladek
27

Esta es una rama de su pregunta, pero si desea imprimir un NSDate con "Hoy" o "Ayer", use la función

- (void)setDoesRelativeDateFormatting:(BOOL)b

para NSDateFormatter

Jon
fuente
3
Idea inteligente. Pero supongo que buscar la cadena "Hoy" se rompería si lo localizas.
Basil Bourque
16

Intentaría normalizar la fecha de hoy a medianoche y la segunda fecha, normalizar a medianoche y luego comparar si es el mismo NSDate.

A partir de un ejemplo de Apple , así es como se normaliza a la medianoche de la fecha de hoy, haga lo mismo para la segunda fecha y compare:

NSCalendar * gregorian = [[NSCalendar alloc]
                               initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents * components =
    [gregorian components:
                 (NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit)
                 fromDate:[NSDate date]];
NSDate * today = [gregorian dateFromComponents:components];
stefanB
fuente
Creo que también debes incluir la era.
Chris Page
¿No debería normalizarse una fecha estableciendo la hora en 12, es decir, mediodía, no medianoche? Dado que la fecha está en GMT, configurarla a mediodía garantiza que las variaciones de zona horaria en ambos sentidos (y que aumenten hasta las 12h solo en ambos sentidos) no 'salten' al día anterior o posterior.
artooras
13

Extensión de trabajo Swift 3 y 4 de la sugerencia de Catfish_Man:

extension Date {

    func isToday() -> Bool {
        return Calendar.current.isDateInToday(self)
    }

}
Benno Kress
fuente
12

No es necesario hacer malabarismos con componentes, épocas y demás.

NSCalendar proporciona un método para obtener el comienzo de una determinada unidad de tiempo para una fecha existente.

Este código tendrá el comienzo de hoy y otra fecha y lo comparará. Si se evalúa como NSOrderedSame, ambas fechas son durante el mismo día, así que hoy.

NSDate *today = nil;
NSDate *beginningOfOtherDate = nil;

NSDate *now = [NSDate date];
[[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit startDate:&today interval:NULL forDate:now];
[[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit startDate:&beginningOfOtherDate interval:NULL forDate:beginningOfOtherDate];

if([today compare:beginningOfOtherDate] == NSOrderedSame) {
    //otherDate is a date in the current day
}
vikingosegundo
fuente
9
extension NSDate {
  func isToday() -> Bool {
    let cal = NSCalendar.currentCalendar()
    var components = cal.components([.Era, .Year, .Month, .Day], fromDate:NSDate())
    let today = cal.dateFromComponents(components)!

    components = cal.components([.Era, .Year, .Month, .Day], fromDate:self)
    let otherDate = cal.dateFromComponents(components)!

    return today.isEqualToDate(otherDate)
}

Trabajó para mí en Swift 2.0

Omar Albeik
fuente
6

Versión rápida de la mejor respuesta:

let cal = NSCalendar.currentCalendar()
var components = cal.components([.Era, .Year, .Month, .Day], fromDate:NSDate())
let today = cal.dateFromComponents(components)!

components = cal.components([.Era, .Year, .Month, .Day], fromDate:aDate);
let otherDate = cal.dateFromComponents(components)!

if(today.isEqualToDate(otherDate)) {
    //do stuff
}
Vojtech Vrbka
fuente
5

Consulte la entrada de documentación de Apple titulada "Realización de cálculos de calendario" [enlace] .

El Listado 13 en esa página sugiere que para determinar la cantidad de medianoches entre días, use:

- (NSInteger)midnightsFromDate:(NSDate *)startDate toDate:(NSDate *)endDate
{
    NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
    NSInteger startDay = [calendar ordinalityOfUnit:NSDayCalendarUnit
                                             inUnit:NSEraCalendarUnit
                                            forDate:startDate];
    NSInteger endDay = [calendar ordinalityOfUnit:NSDayCalendarUnit
                                           inUnit:NSEraCalendarUnit
                                          forDate:endDate];
    return endDay - startDay;
}

Luego puede determinar si dos días son iguales utilizando ese método y ver si devuelve 0 o no.

ArtOfWarfare
fuente
2
¡Tenga en cuenta que una Era comienza a las 00:00 GMT! Es posible que este método no funcione correctamente si una de las fechas (o ambas) no se encuentra en la zona horaria GMT.
Andreas Ley
5

También puede verificar el intervalo de tiempo entre la fecha que tiene y la fecha actual:

[myDate timeIntervalSinceNow]

Esto le dará el intervalo de tiempo, en segundos, entre myDate y la fecha / hora actual.

Enlace .

Editar: Nota para todos: Soy consciente de que [myDate timeIntervalSinceNow] no determina inequívocamente si myDate es hoy.

Dejo esta respuesta tal como está para que si alguien está buscando algo similar y [myDate timeIntervalSinceNow] es útil, puede encontrarlo aquí.

alesplin
fuente
1
Lo que dices es correcto, pero el intervalo realmente no ayuda a verificar si la fecha es hoy.
Jaanus
3
Solo tenga en cuenta que esto le dice si está dentro de las 24 horas desde ahora, pero no si las fechas son las mismas. Una diferencia de un segundo puede ser fechas diferentes y una diferencia de 86399 segundos puede ser la misma fecha.
David Kanarek
@David: cierto. No pensé en eso porque estaba haciendo otras 3 cosas mientras respondía la pregunta, y tu respuesta no se publicó cuando cargué la página.
alesplin
1
Esto no responde a la pregunta en absoluto. Deberías eliminarlo.
vikingosegundo
1
Esta respuesta no es más que ruido. No intenta responder a la pregunta ni es necesario hacer referencia a timeIntervalSinceNowella, ya que está cubierto en muchas otras publicaciones. También manejar la comparación del día con la verificación de segundos fomenta la división propensa a errores en 86400.
vikingosegundo
4

Extensión rápida basada en las mejores respuestas:

extension NSDate {
    func isToday() -> Bool {
        let cal = NSCalendar.currentCalendar()
        if cal.respondsToSelector("isDateInToday:") {
            return cal.isDateInToday(self)
        }
        var components = cal.components((.CalendarUnitEra | .CalendarUnitYear | .CalendarUnitMonth | .CalendarUnitDay), fromDate:NSDate())
        let today = cal.dateFromComponents(components)!

        components = cal.components((.CalendarUnitEra | .CalendarUnitYear | .CalendarUnitMonth | .CalendarUnitDay), fromDate:self);
        let otherDate = cal.dateFromComponents(components)!
        return today.isEqualToDate(otherDate)
    }
}
ZiggyST
fuente
2

Si tiene muchas de estas comparaciones de fechas, entonces las llamadas a calendar:components:fromDate comenzar a tomar mucho tiempo. Según algunos perfiles que he hecho, parecen ser bastante caros.

Digamos que está tratando de determinar cuáles de una serie de fechas, por ejemplo NSArray *datesToCompare, son el mismo día que un día determinado, digamos NSDate *baseDate, entonces puede usar algo como lo siguiente (parcialmente adaptado de una respuesta anterior):

NSDate *baseDate = [NSDate date];

NSArray *datesToCompare = [NSArray arrayWithObjects:[NSDate date], 
                           [NSDate dateWithTimeIntervalSinceNow:100],
                           [NSDate dateWithTimeIntervalSinceNow:1000],
                           [NSDate dateWithTimeIntervalSinceNow:-10000],
                           [NSDate dateWithTimeIntervalSinceNow:100000],
                           [NSDate dateWithTimeIntervalSinceNow:1000000],
                           [NSDate dateWithTimeIntervalSinceNow:50],
                           nil];

// determine the NSDate for midnight of the base date:
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* comps = [calendar components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) 
                                       fromDate:baseDate];
NSDate* theMidnightHour = [calendar dateFromComponents:comps];

// set up a localized date formatter so we can see the answers are right!
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];

// determine which dates in an array are on the same day as the base date:
for (NSDate *date in datesToCompare) {
    NSTimeInterval interval = [date timeIntervalSinceDate:theMidnightHour];
    if (interval >= 0 && interval < 60*60*24) {
        NSLog(@"%@ is on the same day as %@", [dateFormatter stringFromDate:date], [dateFormatter stringFromDate:baseDate]);
    }
    else {
        NSLog(@"%@ is NOT on the same day as %@", [dateFormatter stringFromDate:date], [dateFormatter stringFromDate:baseDate]);
    }
}

Salida:

Nov 23, 2011 1:32:00 PM is on the same day as Nov 23, 2011 1:32:00 PM
Nov 23, 2011 1:33:40 PM is on the same day as Nov 23, 2011 1:32:00 PM
Nov 23, 2011 1:48:40 PM is on the same day as Nov 23, 2011 1:32:00 PM
Nov 23, 2011 10:45:20 AM is on the same day as Nov 23, 2011 1:32:00 PM
Nov 24, 2011 5:18:40 PM is NOT on the same day as Nov 23, 2011 1:32:00 PM
Dec 5, 2011 3:18:40 AM is NOT on the same day as Nov 23, 2011 1:32:00 PM
Nov 23, 2011 1:32:50 PM is on the same day as Nov 23, 2011 1:32:00 PM
Julian Richardson
fuente
Gracias por esto. La forma aceptada era demasiado CPU para nuestra aplicación, por lo que necesitábamos optimizar esto.
Accatyyc
1
Este código fallará para el horario de verano. Todos los que usan código con el infame número 86,400 merecen el dolor de cabeza que puede causar.
vikingosegundo
2

¡Hay una manera más fácil que muchas de las respuestas anteriores!

NSDate *date = ... // The date you wish to test
NSCalendar *calendar = [NSCalendar currentCalendar];

if([calendar isDateInToday:date]) {
    //do stuff
}
aryland
fuente
1
¡Igual que la respuesta de Catfish_Man!
Thanh Pham
0

Esto probablemente podría ser modificado como una categoría NSDate, pero utilicé:

// Seconds per day (24h * 60m * 60s)
#define kSecondsPerDay 86400.0f

+ (BOOL) dateIsToday:(NSDate*)dateToCheck
{
    // Split today into components
    NSCalendar* gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents* comps = [gregorian components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit|NSSecondCalendarUnit) 
                                        fromDate:[NSDate date]];

    // Set to this morning 00:00:00
    [comps setHour:0];
    [comps setMinute:0];
    [comps setSecond:0];
    NSDate* theMidnightHour = [gregorian dateFromComponents:comps];
    [gregorian release];

    // Get time difference (in seconds) between date and then
    NSTimeInterval diff = [dateToCheck timeIntervalSinceDate:theMidnightHour];
    return ( diff>=0.0f && diff<kSecondsPerDay );
}

(Sin embargo, comparar las dos cadenas de fecha como en la pregunta original casi se siente 'más limpio' ...)

León
fuente
¿Por qué incluir horas, minutos y segundos al crear compscuando los está configurando inmediatamente en cero? Además, creo que debes incluir la era.
Chris Page
Tenga en cuenta que si bien esto funcionará en casi todos los casos, existen cosas como los segundos bisiestos. No sé si o cómo la clase NSDate de Apple los maneja, pero hay alguna posibilidad de que esto no funcione. Esto es más un "sabía" en lugar de una crítica de su método, ya que son extremadamente raros (24 en la historia).
David Kanarek el
¿Qué pasa con las fechas que no duran 24 horas, pero que son 23 o 25 debido al horario de verano?
vikingosegundo
0

para iOS7 y versiones anteriores:

//this is now => need that for the current date
NSDate * now = [NSDate date];

NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[calendar setTimeZone:[NSTimeZone systemTimeZone]];

NSDateComponents * components = [calendar components:( NSYearCalendarUnit|    NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit) fromDate: now];

[components setMinute:0];
[components setHour:0];
[components setSecond:0];

//this is Today's Midnight
NSDate *todaysMidnight = [calendar dateFromComponents: components];



//now timeIntervals since Midnight => in seconds
NSTimeInterval todayTimeInterval = [now timeIntervalSinceDate: todaysMidnight];

//now timeIntervals since OtherDate => in seconds
NSTimeInterval otherDateTimeInterval = [now timeIntervalSinceDate: otherDate];

if(otherDateTimeInterval > todayTimeInterval) //otherDate is not in today
{
    if((otherDateTimeInterval - todayTimeInterval) <= 86400) //86400 == a day total seconds
    {
        @"yesterday";
    }
    else
    {
        @"earlier";
    }
}
else
{
    @"today";
}


now = nil;
calendar = nil;
components = nil;
todaysMidnight = nil;

NSLog("Thank you :-)");
usuario3744424
fuente
0

La solución correcta y segura sin desenvolver la fuerza, trabajando en Swift 2.2 y antes de iOS 8:

func isToday() -> Bool {
    let calendar = NSCalendar.currentCalendar()
    if #available(iOS 8.0, *) {
        return calendar.isDateInToday(self)
    }

    let todayComponents = calendar.components([.Era, .Year, .Month, .Day], fromDate:NSDate())
    let dayComponents = calendar.components([.Era, .Year, .Month, .Day], fromDate:self)

    guard let today = calendar.dateFromComponents(todayComponents),
        day = calendar.dateFromComponents(dayComponents) else {
        return false
    }

    return today.compare(day) == .OrderedSame
}
fpg1503
fuente
-1

Aquí está mi respuesta de 2 centavos que se basa en la respuesta aceptada pero que también admite la API más nueva. Nota: uso el calendario gregoriano ya que la mayoría de las marcas de tiempo son GMT, pero cambio el tuyo como mejor te parezca

func isDateToday(date: NSDate) -> Bool {
    let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
    if calendar.respondsToSelector("isDateInToday:") {
        return calendar.isDateInToday(date)
    }
    let dateComponents = NSCalendarUnit.CalendarUnitYear | NSCalendarUnit.CalendarUnitMonth | NSCalendarUnit.CalendarUnitDay
    let today = calendar.dateFromComponents(calendar.components(dateComponents, fromDate: NSDate()))!
    let dateToCompare = calendar.dateFromComponents(calendar.components(dateComponents, fromDate: date))!

    return dateToCompare == today
}
Daniel Galasko
fuente
-3

Mi solución es calcular cuántos días pasaron desde 1970 por división y comparar la parte entera

#define kOneDay (60*60*24)
- (BOOL)isToday {
  NSInteger offset = [[NSTimeZone defaultTimeZone] secondsFromGMT];

  NSInteger days =[self timeIntervalSince1970] + offset;
  NSInteger currentDays = [[NSDate date] timeIntervalSince1970] + offset;
  return (days / kOneDay == currentDays / kOneDay);
}
stcui
fuente
Eso en realidad no es una mala solución. No estoy seguro de por qué fue tan rechazado. El uso de la división de enteros cortará el resto y comparará solo el valor de "días" del tiempo largo de Unix, después de corregir GMT. Creo que esta es la solución más limpia de todas para ser bastante franco.
devios1
1
@chaiguy: esta es una mala solución, como se supone, que cada día dura 24 horas, lo que no es el caso. Debido al horario de verano, los días pueden durar 23, 24 o 25 horas.
vikingosegundo
Hmm eso es verdad. Sin embargo, el promedio por año es exactamente de 24 horas, por lo que solo estará potencialmente apagado en una hora en el peor de los casos, según lo veo. Quizás eso podría verificarse explícitamente.
devios1
2
@chaiguy: no. simplemente nunca use un código que contenga 60 * 60 * 24. o vive con tu dolor de cabeza.
vikingosegundo
"secondsFromGMT" cambia abruptamente dos veces al año en la mayoría de los lugares del mundo. Mucho más seguro usar los métodos NSCalendar.
gnasher729
-4
NSDate *dateOne = yourDate;
NSDate *dateTwo = [NSDate date];  

switch ([dateOne compare:dateTwo])
{  
    case NSOrderedAscending:  
        NSLog(@”NSOrderedAscending”);  
        break;  

    case NSOrderedSame: 
        NSLog(@”NSOrderedSame”);  
        break;

    case NSOrderedDescending:  
        NSLog(@”NSOrderedDescending”);  
        break;  
}  
rajesh
fuente
44
Esto no le dice si ambos NSDates son la misma fecha, solo cuál es anterior, incluso si son el mismo día.
David Kanarek